2538 lines
84 KiB
Go
2538 lines
84 KiB
Go
package main
|
||
|
||
import (
|
||
"context"
|
||
"crypto/rand"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"encoding/xml"
|
||
"fmt"
|
||
"io/ioutil"
|
||
"log"
|
||
"math/big"
|
||
"net/http"
|
||
"net/mail"
|
||
"os"
|
||
|
||
"pocketbase/utils"
|
||
"strings"
|
||
"time"
|
||
|
||
firebase "firebase.google.com/go"
|
||
"firebase.google.com/go/messaging"
|
||
"github.com/labstack/echo/v5"
|
||
"github.com/pocketbase/dbx"
|
||
"github.com/pocketbase/pocketbase"
|
||
"github.com/pocketbase/pocketbase/apis"
|
||
"github.com/pocketbase/pocketbase/core"
|
||
"github.com/pocketbase/pocketbase/models"
|
||
|
||
// "github.com/pocketbase/pocketbase/tools/hook"
|
||
|
||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||
"github.com/pocketbase/pocketbase/tools/types"
|
||
"google.golang.org/api/option"
|
||
)
|
||
|
||
type EmailConfigurationResponse struct {
|
||
Page int `json:"page"`
|
||
PerPage int `json:"perPage"`
|
||
TotalItems int `json:"totalItems"`
|
||
TotalPages int `json:"totalPages"`
|
||
Items []struct {
|
||
Type string `json:"type"`
|
||
Email string `json:"email"`
|
||
} `json:"items"`
|
||
}
|
||
|
||
type GenericData struct {
|
||
XMLName xml.Name `xml:"GenericData"`
|
||
DataSet []Obs `xml:"DataSet>Obs"`
|
||
}
|
||
|
||
type Obs struct {
|
||
ObsKey []ObsKey `xml:"ObsKey>Value"` // Dynamic key-value pairs
|
||
ObsValue ObsValue `xml:"ObsValue"`
|
||
}
|
||
|
||
type ObsKey struct {
|
||
ID string `xml:"id,attr"` // Attribute for 'id'
|
||
Value string `xml:"value,attr"` // Attribute for 'value'
|
||
}
|
||
|
||
type ObsValue struct {
|
||
Value string `xml:"value,attr"` // Attribute for 'value'
|
||
}
|
||
|
||
// Helper function to map ObsKey into a dictionary
|
||
func mapObsKey(keys []ObsKey) map[string]string {
|
||
result := map[string]string{}
|
||
for _, key := range keys {
|
||
result[key.ID] = key.Value
|
||
}
|
||
return result
|
||
}
|
||
|
||
// Helper function to clean XML namespace prefixes
|
||
func removeNamespace(xmlBytes []byte) string {
|
||
return strings.ReplaceAll(string(xmlBytes), "generic:", "")
|
||
}
|
||
|
||
// Function to fetch XML from an API and convert it to JSON
|
||
func fetchAndConvertXMLToJSON(apiURL string, outputDir string, fileName string, tableName string, id string) ([]map[string]interface{}, error) {
|
||
// Call the API and get the XML response
|
||
resp, err := http.Get(apiURL)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
// Read the response body
|
||
body, err := ioutil.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Clean the XML by removing namespace prefixes
|
||
cleanXML := []byte(removeNamespace(body))
|
||
|
||
// Parse the cleaned XML data
|
||
var data GenericData
|
||
err = xml.Unmarshal(cleanXML, &data)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Transform the data for JSON output
|
||
response := []map[string]interface{}{}
|
||
for _, obs := range data.DataSet {
|
||
obsMap := map[string]interface{}{
|
||
"ObsKey": mapObsKey(obs.ObsKey),
|
||
"ObsValue": obs.ObsValue,
|
||
}
|
||
response = append(response, obsMap)
|
||
}
|
||
|
||
// Convert the response to JSON
|
||
jsonData, err := json.MarshalIndent(response, "", " ")
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Save the JSON data to a file if required
|
||
// Ensure the folder exists
|
||
if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Save the file
|
||
file := fmt.Sprintf("%s/%s.json", outputDir, fileName)
|
||
if err := ioutil.WriteFile(file, jsonData, 0644); err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// Find the charts record
|
||
record, err := app.Dao().FindRecordById(tableName, id)
|
||
if err != nil {
|
||
// return c.JSON(500, map[string]interface{}{"code": 500,"message": "Error finding user.",})
|
||
}
|
||
record.Set("file_status", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
// return c.JSON(500, map[string]interface{}{"code": 500,"message": "Failed to verify user.",})
|
||
}
|
||
|
||
return response, nil
|
||
}
|
||
|
||
// Function to read JSON data from a file
|
||
func readJSONFromFile(folderName, fileName string) ([]map[string]interface{}, error) {
|
||
// Construct the file path dynamically using the folder name
|
||
filePath := fmt.Sprintf("%s/%s.json", folderName, fileName)
|
||
|
||
// Read and return the JSON content from the file
|
||
fileContent, err := ioutil.ReadFile(filePath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to read file '%s': %w", filePath, err)
|
||
}
|
||
|
||
// Parse the JSON content from the file
|
||
var jsonResponse []map[string]interface{}
|
||
if err := json.Unmarshal(fileContent, &jsonResponse); err != nil {
|
||
return nil, fmt.Errorf("failed to parse JSON from file '%s': %w", filePath, err)
|
||
}
|
||
|
||
return jsonResponse, nil
|
||
}
|
||
|
||
type ResponseItem struct {
|
||
ObsKey map[string]string `json:"ObsKey"`
|
||
ObsValue map[string]string `json:"ObsValue"`
|
||
}
|
||
|
||
type FilterJsonData struct {
|
||
Order int `json:"order"`
|
||
En string `json:"en"`
|
||
Ar string `json:"ar"`
|
||
}
|
||
|
||
type FilterGroup struct {
|
||
FilterKey string `json:"filter_key"`
|
||
FilterData []interface{} `json:"filter_data"`
|
||
FilterTextAndOrder FilterJsonData `json:"filter_text_and_order"`
|
||
}
|
||
|
||
func generateFilters(responseRaw []map[string]interface{}, filterKeys []string, filterJsonData map[string]FilterJsonData) ([]FilterGroup, error) {
|
||
// Transform responseRaw to []ResponseItem
|
||
var response []ResponseItem
|
||
responseBytes, err := json.Marshal(responseRaw) // Marshal raw data to JSON
|
||
if err != nil {
|
||
log.Printf("Failed to marshal raw response: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
if err := json.Unmarshal(responseBytes, &response); err != nil { // Unmarshal to []ResponseItem
|
||
log.Printf("Failed to unmarshal response: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Create a map to hold unique filter data
|
||
filterMap := make(map[string]map[string]struct{})
|
||
for _, key := range filterKeys {
|
||
filterMap[key] = make(map[string]struct{})
|
||
}
|
||
|
||
// Populate filter data dynamically
|
||
for _, item := range response {
|
||
for _, key := range filterKeys {
|
||
if value, exists := item.ObsKey[key]; exists {
|
||
filterMap[key][value] = struct{}{}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Convert filter map to the desired structure
|
||
var filters []FilterGroup
|
||
for key, values := range filterMap {
|
||
filterData := make([]interface{}, 0, len(values))
|
||
for value := range values {
|
||
filterData = append(filterData, value)
|
||
}
|
||
|
||
if key == "REF_AREA" {
|
||
filterData = []interface{}{"AE-AZ", "AE-DU", "AE-SH", "AE-AJ", "AE-UQ", "AE-RK", "AE-FJ"}
|
||
}
|
||
filters = append(filters, FilterGroup{
|
||
FilterKey: key,
|
||
FilterData: filterData,
|
||
FilterTextAndOrder: filterJsonData[key],
|
||
})
|
||
}
|
||
|
||
return filters, nil
|
||
}
|
||
|
||
// Function to fetch filter data of dataset
|
||
func fetchfilterJSON(kpi string) ([]FilterGroup, error) {
|
||
|
||
// Define struct for the result
|
||
var dataSetFilter struct {
|
||
Id string `db:"id" json:"id"`
|
||
FullDataUrl string `db:"full_data_url" json:"full_data_url"`
|
||
FilterKeys string `db:"filter_keys" json:"filter_keys"`
|
||
FileStatus bool `db:"file_status" json:"file_status"`
|
||
DataSet string `db:"dataset" json:"dataset"`
|
||
Kpi string `db:"kpi" json:"kpi"`
|
||
ExclusionFiltersJSON string `db:"exclusion_filters_array" json:"exclusion_filters_array"`
|
||
FilterJson string `db:"filter_json" json:"filter_json"`
|
||
}
|
||
|
||
// Query to get one row from the data_set_filter table
|
||
err := app.DB().
|
||
Select("id", "full_data_url", "filter_keys", "file_status", "dataset", "kpi", "filter_json", "exclusion_filters_array").
|
||
From("data_set_filter").
|
||
Where(dbx.HashExp{"kpi": kpi}).
|
||
One(&dataSetFilter)
|
||
|
||
if err != nil {
|
||
log.Printf("Failed to fetch data_set_filter data: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Use a map to store the dynamic JSON keys
|
||
var filterJson map[string]FilterJsonData
|
||
|
||
// Parse the JSON
|
||
err = json.Unmarshal([]byte(dataSetFilter.FilterJson), &filterJson)
|
||
if err != nil {
|
||
log.Fatalf("Error parsing JSON: %v", err)
|
||
}
|
||
|
||
// Fetch and convert XML to JSON if file is not saved
|
||
if !dataSetFilter.FileStatus { // Check if FileStatus is false
|
||
_, err := fetchAndConvertXMLToJSON(dataSetFilter.FullDataUrl, "filter_files", dataSetFilter.Kpi, "data_set_filter", dataSetFilter.Id)
|
||
if err != nil {
|
||
log.Printf("Failed to process URL '%s': %v", dataSetFilter.FullDataUrl, err)
|
||
return nil, err
|
||
}
|
||
}
|
||
|
||
// Read the JSON data from the file
|
||
response, err := readJSONFromFile("filter_files", dataSetFilter.Kpi)
|
||
if err != nil {
|
||
log.Printf("Failed to read JSON for Kpi filter '%s': %v", dataSetFilter.Kpi, err)
|
||
return nil, err
|
||
}
|
||
|
||
// Parse the filter keys
|
||
filterKeys := strings.Split(dataSetFilter.FilterKeys, ",")
|
||
|
||
// Generate filters
|
||
filters, err := generateFilters(response, filterKeys, filterJson)
|
||
if err != nil {
|
||
log.Printf("Failed to generate filters: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
// Parse exclusion filters JSON if it's not empty
|
||
var exclusionFilters []utils.FilterGroup
|
||
if dataSetFilter.ExclusionFiltersJSON != "" {
|
||
err = json.Unmarshal([]byte(dataSetFilter.ExclusionFiltersJSON), &exclusionFilters)
|
||
if err != nil {
|
||
log.Printf("Failed to parse exclusion filters JSON: %v", err)
|
||
return nil, err
|
||
}
|
||
// log.Printf("Parsed Exclusion Filters: %+v", exclusionFilters)
|
||
}
|
||
|
||
// Convert filters from []FilterGroup to []utils.FilterGroup manually
|
||
var utilsFilters []utils.FilterGroup
|
||
for _, f := range filters {
|
||
utilsFilters = append(utilsFilters, convertToUtilsFilterGroup(f))
|
||
}
|
||
|
||
// Apply the filter removal logic only if exclusionFilters has values
|
||
if len(exclusionFilters) > 0 {
|
||
filteredResult := utils.RemoveExcludedFilters(utilsFilters, exclusionFilters)
|
||
|
||
// Convert back to []FilterGroup before returning
|
||
var finalFilters []FilterGroup
|
||
for _, f := range filteredResult {
|
||
finalFilters = append(finalFilters, convertToFilterGroup(f))
|
||
}
|
||
|
||
return finalFilters, nil
|
||
}
|
||
|
||
// If no exclusion filters, return the original filters
|
||
return filters, nil
|
||
|
||
}
|
||
|
||
// Convert FilterGroup to utils.FilterGroup
|
||
func convertToUtilsFilterGroup(f FilterGroup) utils.FilterGroup {
|
||
return utils.FilterGroup{
|
||
FilterKey: f.FilterKey,
|
||
FilterData: f.FilterData,
|
||
FilterTextAndOrder: utils.FilterJsonData{
|
||
Order: f.FilterTextAndOrder.Order,
|
||
En: f.FilterTextAndOrder.En,
|
||
Ar: f.FilterTextAndOrder.Ar,
|
||
},
|
||
}
|
||
}
|
||
|
||
// Convert utils.FilterGroup to FilterGroup
|
||
func convertToFilterGroup(f utils.FilterGroup) FilterGroup {
|
||
return FilterGroup{
|
||
FilterKey: f.FilterKey,
|
||
FilterData: f.FilterData,
|
||
FilterTextAndOrder: FilterJsonData{
|
||
Order: f.FilterTextAndOrder.Order,
|
||
En: f.FilterTextAndOrder.En,
|
||
Ar: f.FilterTextAndOrder.Ar,
|
||
},
|
||
}
|
||
}
|
||
|
||
// LanguageSource represents the source language data structure
|
||
type LanguageSource struct {
|
||
Dataset string `json:"dataset"`
|
||
Key string `json:"key"`
|
||
Value string `json:"value"`
|
||
ValueEn string `json:"value_en"`
|
||
ValueAr string `json:"value_ar"`
|
||
}
|
||
|
||
func TranslateFilters(app *pocketbase.PocketBase, dataset string, filterGroups []FilterGroup, langKey string) ([]FilterGroup, error) {
|
||
// Fetch source data from the PocketBase collection
|
||
param := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset})
|
||
records, err := app.Dao().FindRecordsByExpr("charts_variables", param)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("failed to fetch source data: %w", err)
|
||
}
|
||
|
||
// Parse the source data into a slice of LanguageSource
|
||
var sourceData []LanguageSource
|
||
for _, record := range records {
|
||
source := LanguageSource{
|
||
Dataset: record.GetString("dataset"),
|
||
Key: record.GetString("key"),
|
||
Value: record.GetString("value"),
|
||
ValueEn: record.GetString("value_en"),
|
||
ValueAr: record.GetString("value_ar"),
|
||
}
|
||
sourceData = append(sourceData, source)
|
||
}
|
||
|
||
// Translate the filter data
|
||
for i, filter := range filterGroups {
|
||
|
||
// Create a map for quick lookup of translations
|
||
translationMap := make(map[string]string)
|
||
for _, source := range sourceData {
|
||
var value string
|
||
switch langKey {
|
||
case "value_en":
|
||
value = source.ValueEn
|
||
case "value_ar":
|
||
value = source.ValueAr
|
||
default:
|
||
continue
|
||
}
|
||
if source.Key == filter.FilterKey {
|
||
translationMap[source.Value] = value
|
||
}
|
||
|
||
}
|
||
|
||
if filter.FilterKey == "TIME_PERIOD" {
|
||
// Skip translation for TIME_PERIOD
|
||
continue
|
||
}
|
||
for j, v := range filter.FilterData {
|
||
// Type assertion for interface{} to string
|
||
value, ok := v.(string)
|
||
if !ok {
|
||
return nil, fmt.Errorf("unexpected type for filter data: %v", v)
|
||
}
|
||
|
||
// Translate the value if it exists in the map
|
||
if translatedValue, exists := translationMap[value]; exists {
|
||
filterGroups[i].FilterData[j] = translatedValue
|
||
}
|
||
}
|
||
}
|
||
|
||
return filterGroups, nil
|
||
}
|
||
|
||
// Generic function to filter JSON data dynamically based on key-value pairs
|
||
func filterDynamicChartData(chartData []map[string]interface{}, keyToFilter, valueToExclude string) []map[string]interface{} {
|
||
var filteredData []map[string]interface{}
|
||
|
||
for _, item := range chartData {
|
||
obsKey, ok := item["ObsKey"].(map[string]interface{})
|
||
if !ok {
|
||
// Skip items without "ObsKey" or malformed data
|
||
continue
|
||
}
|
||
|
||
// Check if the key exists and matches the value to exclude
|
||
if obsKey[keyToFilter] == valueToExclude {
|
||
continue
|
||
}
|
||
|
||
// Add item to filtered data
|
||
filteredData = append(filteredData, item)
|
||
}
|
||
|
||
return filteredData
|
||
}
|
||
|
||
func generateToken() string {
|
||
bytes := make([]byte, 32)
|
||
_, err := rand.Read(bytes)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
return hex.EncodeToString(bytes)
|
||
}
|
||
|
||
var app *pocketbase.PocketBase
|
||
|
||
func main() {
|
||
const baseUrl = "https://pb.venbait.in"
|
||
|
||
app = pocketbase.New()
|
||
|
||
// Hook to send notifications when a new record is created
|
||
app.OnRecordAfterCreateRequest("notification").Add(func(e *core.RecordCreateEvent) error {
|
||
title_en := e.Record.GetString("title_en")
|
||
message_en := e.Record.GetString("message_en")
|
||
title_ar := e.Record.GetString("title_ar")
|
||
message_ar := e.Record.GetString("message_ar")
|
||
notificationID := e.Record.GetString("id") // Get the new notification ID
|
||
|
||
// Initialize Firebase
|
||
opt := option.WithCredentialsFile("firebase-adminsdk.json")
|
||
firebaseApp, err := firebase.NewApp(context.Background(), nil, opt)
|
||
if err != nil {
|
||
log.Fatalf("Error initializing Firebase: %v", err)
|
||
}
|
||
|
||
fcmClient, err := firebaseApp.Messaging(context.Background())
|
||
if err != nil {
|
||
log.Fatalf("Error getting FCM client: %v", err)
|
||
}
|
||
|
||
// Fetch users with valid device tokens
|
||
users, err := app.Dao().FindRecordsByExpr("users", nil) // Adjust collection name if different
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
for _, user := range users {
|
||
token := user.GetString("device_token")
|
||
language := user.GetString("language")
|
||
if token != "" {
|
||
// tokens = append(tokens, token)
|
||
var message *messaging.Message
|
||
if language == "en" {
|
||
message = &messaging.Message{
|
||
Token: token,
|
||
Notification: &messaging.Notification{
|
||
Title: title_en,
|
||
Body: message_en,
|
||
},
|
||
}
|
||
} else {
|
||
message = &messaging.Message{
|
||
Token: token,
|
||
Notification: &messaging.Notification{
|
||
Title: title_ar,
|
||
Body: message_ar,
|
||
},
|
||
}
|
||
|
||
}
|
||
|
||
response, err := fcmClient.Send(context.Background(), message)
|
||
if err != nil {
|
||
|
||
if err.Error() == "messaging/registration-token-not-registered" {
|
||
log.Println("⚠️ Device token is invalid. Remove it from the database.")
|
||
// Here you should remove the token from your database
|
||
} else {
|
||
log.Printf("Error sending message: %v", err)
|
||
}
|
||
} else {
|
||
fmt.Printf("✅ Successfully sent message: %s\n", response)
|
||
|
||
// Update the pushed_notification field
|
||
var pushedNotifications []string
|
||
|
||
// Get existing pushed_notification JSON field
|
||
pushedNotificationsJSON := user.GetString("pushed_notification")
|
||
if pushedNotificationsJSON != "" {
|
||
err := json.Unmarshal([]byte(pushedNotificationsJSON), &pushedNotifications)
|
||
if err != nil {
|
||
log.Printf("⚠️ Error parsing pushed_notification for user %s: %v", user.GetString("id"), err)
|
||
continue
|
||
}
|
||
}
|
||
|
||
// Append the new notification ID if not already present
|
||
alreadyExists := false
|
||
for _, existingID := range pushedNotifications {
|
||
if existingID == notificationID {
|
||
alreadyExists = true
|
||
break
|
||
}
|
||
}
|
||
if !alreadyExists {
|
||
pushedNotifications = append(pushedNotifications, notificationID)
|
||
}
|
||
|
||
// Convert back to JSON
|
||
updatedPushedNotifications, err := json.Marshal(pushedNotifications)
|
||
if err != nil {
|
||
log.Printf("⚠️ Error marshalling pushed_notification JSON: %v", err)
|
||
continue
|
||
}
|
||
|
||
// Update the user record in the database
|
||
user.Set("pushed_notification", string(updatedPushedNotifications))
|
||
if err := app.Dao().SaveRecord(user); err != nil {
|
||
log.Printf("❌ Error updating pushed_notification for user %s: %v", user.GetString("id"), err)
|
||
} else {
|
||
fmt.Printf("✅ Updated pushed_notification for user %s\n", user.GetString("id"))
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
|
||
return nil
|
||
})
|
||
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
e.Router.GET("/pw/auth/confirm-password-reset/:token", func(c echo.Context) error {
|
||
return c.File("pb_public/confirm_password_reset.html")
|
||
})
|
||
|
||
e.Router.GET("/api/auth/request-password-reset", func(c echo.Context) error {
|
||
email := c.QueryParam("email")
|
||
if email == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Email is required"})
|
||
}
|
||
|
||
// Find user by email
|
||
user, err := app.Dao().FindFirstRecordByData("users", "email", email)
|
||
if err != nil {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
|
||
}
|
||
name := user.GetString("name")
|
||
// Generate token
|
||
token := generateToken()
|
||
expiration := time.Now().Add(30 * time.Minute).UTC()
|
||
|
||
// Store token in the database
|
||
user.Set("pw_reset_token", token)
|
||
user.Set("pw_reset_token_expiry", expiration)
|
||
|
||
if err := app.Dao().SaveRecord(user); err != nil {
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update user record"})
|
||
}
|
||
|
||
// Send email
|
||
subject := "Reset your FCSC password"
|
||
// resetURL := fmt.Sprintf("%s/api/auth/reset-password?token=%s", baseUrl, token)
|
||
resetURL := fmt.Sprintf("https://www.fcsc.com/forgot-password/%s", email)
|
||
|
||
body := fmt.Sprintf(`
|
||
<p>Hello,</p>
|
||
<p>Click on the link below to reset your password.</p>
|
||
<p>
|
||
<a href="%s" target="_blank" rel="noopener" style="color: blue; text-decoration: underline;">Reset password</a>
|
||
</p>
|
||
<p><i>If you didn't ask to reset your password, you can ignore this email.</i></p>
|
||
<p>
|
||
Thanks,<br/>
|
||
Your App Team<br/>
|
||
</p>
|
||
`, resetURL)
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send verification email to user: %v", err)
|
||
return err
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, map[string]string{"message": "Reset link sent to your email"})
|
||
})
|
||
|
||
e.Router.GET("/api/auth/reset-password", func(c echo.Context) error {
|
||
token := c.QueryParam("token")
|
||
if token == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Token is required"})
|
||
}
|
||
|
||
// Find user with the token
|
||
user, err := app.Dao().FindFirstRecordByData("users", "pw_reset_token", token)
|
||
if err != nil {
|
||
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"})
|
||
}
|
||
|
||
// Convert PocketBase `types.DateTime` to `time.Time`
|
||
expiryTime := user.GetDateTime("pw_reset_token_expiry").Time()
|
||
log.Printf("expiryTime : '%v'", expiryTime)
|
||
log.Printf("time.Now : '%v'", time.Now().UTC())
|
||
if expiryTime.Before(time.Now().UTC()) {
|
||
return c.File("pb_public/token_expired.html")
|
||
// return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Token expired"})
|
||
}
|
||
|
||
// return c.JSON(http.StatusOK, map[string]string{"message": "Token is valid"})
|
||
|
||
// Serve the reset page
|
||
return c.File("pb_public/confirm_password_reset.html")
|
||
})
|
||
|
||
e.Router.GET("/api/login_success", func(c echo.Context) error {
|
||
|
||
userID := c.QueryParam("id")
|
||
if userID == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "User ID is required"})
|
||
}
|
||
|
||
// Find user by ID
|
||
record, err := app.Dao().FindRecordById("users", userID)
|
||
if err != nil {
|
||
log.Printf("User not found: %v", err)
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"})
|
||
}
|
||
|
||
// Get current login count and convert it properly
|
||
var loginCount int
|
||
if count, ok := record.Get("login_count").(float64); ok {
|
||
loginCount = int(count) // Convert float64 to int
|
||
} else {
|
||
loginCount = 0 // Default if not set
|
||
}
|
||
|
||
// Increment login count
|
||
loginCount++
|
||
record.Set("login_count", loginCount)
|
||
|
||
// Save the updated record
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
log.Printf("Failed to update login count: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update login count"})
|
||
}
|
||
|
||
log.Printf("User %s login count updated to %d", userID, loginCount)
|
||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||
"message": "Success",
|
||
"login_count": loginCount,
|
||
})
|
||
|
||
})
|
||
|
||
e.Router.GET("/api/getNotification", func(c echo.Context) error {
|
||
|
||
language := c.QueryParam("language")
|
||
if language == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "language is required"})
|
||
}
|
||
|
||
categoryColumn := "category_" + language
|
||
titleColumn := "title_" + language
|
||
messageColumn := "message_" + language
|
||
|
||
id := c.QueryParam("id")
|
||
|
||
if id != "" {
|
||
// Define a single notification struct, not a slice
|
||
var notification struct {
|
||
Id string `db:"id" json:"id"`
|
||
Category string `db:"category" json:"category"`
|
||
Title string `db:"title" json:"title"`
|
||
Message string `db:"message" json:"message"`
|
||
Created string `db:"created" json:"created"`
|
||
}
|
||
|
||
query := `SELECT id, created, ` + categoryColumn + ` AS category, ` + titleColumn + ` AS title, ` + messageColumn + ` AS message FROM notification WHERE id = {:id}`
|
||
err := app.DB().NewQuery(query).Bind(dbx.Params{"id": id}).One(¬ification)
|
||
|
||
if err != nil {
|
||
log.Printf("notification not found: %v", err)
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "notification not found"})
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||
"message": "Success",
|
||
"data": notification,
|
||
})
|
||
}
|
||
|
||
// Fetch all notifications if no ID is provided
|
||
var notifications []struct {
|
||
Id string `db:"id" json:"id"`
|
||
Category string `db:"category" json:"category"`
|
||
Title string `db:"title" json:"title"`
|
||
Message string `db:"message" json:"message"`
|
||
Created string `db:"created" json:"created"`
|
||
}
|
||
|
||
query := `SELECT id, created, ` + categoryColumn + ` AS category, ` + titleColumn + ` AS title, ` + messageColumn + ` AS message FROM notification`
|
||
err := app.DB().NewQuery(query).All(¬ifications)
|
||
|
||
if err != nil {
|
||
log.Printf("notifications not found: %v", err)
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "notifications not found"})
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||
"message": "Success",
|
||
"data": notifications,
|
||
})
|
||
})
|
||
|
||
e.Router.GET("/api/removeNotification", func(c echo.Context) error {
|
||
userID := c.QueryParam("user_id")
|
||
notificationID := c.QueryParam("notification_id")
|
||
|
||
if userID == "" || notificationID == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "user_id and notification_id are required"})
|
||
}
|
||
|
||
// Fetch the user by ID
|
||
user, err := app.Dao().FindRecordById("users", userID)
|
||
if err != nil {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"})
|
||
}
|
||
|
||
var pushedNotifications []string
|
||
|
||
// Get existing pushed_notification JSON field
|
||
pushedNotificationsJSON := user.GetString("pushed_notification")
|
||
if pushedNotificationsJSON != "" {
|
||
err := json.Unmarshal([]byte(pushedNotificationsJSON), &pushedNotifications)
|
||
if err != nil {
|
||
log.Printf("⚠️ Error parsing pushed_notification for user %s: %v", userID, err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to parse pushed_notification"})
|
||
}
|
||
}
|
||
|
||
// Remove the notification ID if it exists
|
||
updatedNotifications := []string{}
|
||
for _, existingID := range pushedNotifications {
|
||
if existingID != notificationID {
|
||
updatedNotifications = append(updatedNotifications, existingID)
|
||
}
|
||
}
|
||
|
||
// Convert updated array back to JSON
|
||
updatedJSON, err := json.Marshal(updatedNotifications)
|
||
if err != nil {
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update pushed_notification"})
|
||
}
|
||
|
||
// Update the user record with the new pushed_notification field
|
||
user.Set("pushed_notification", string(updatedJSON))
|
||
if err := app.Dao().SaveRecord(user); err != nil {
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to save updated notifications"})
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||
"message": "success",
|
||
"pushed_notification": updatedNotifications,
|
||
})
|
||
})
|
||
|
||
e.Router.GET("/api/pushNotification", func(c echo.Context) error {
|
||
|
||
// type Request struct {
|
||
// DeviceToken string `json:"device_token"`
|
||
// }
|
||
|
||
// var requestData Request
|
||
// log.Printf("🔥 requestData: '%v'", requestData)
|
||
// // Validate required fields
|
||
// if requestData.DeviceToken == "" {
|
||
// return c.JSON(http.StatusBadRequest, map[string]string{"error": "DeviceToken is required"})
|
||
// }
|
||
|
||
// DeviceToken := requestData.DeviceToken
|
||
|
||
DeviceToken := c.QueryParam("device_token")
|
||
|
||
// Load Firebase credentials JSON file
|
||
opt := option.WithCredentialsFile("firebase-adminsdk.json")
|
||
app, err := firebase.NewApp(context.Background(), nil, opt)
|
||
if err != nil {
|
||
log.Printf("🔥 Error initializing Firebase App: '%v'", err)
|
||
}
|
||
|
||
client, err := app.Messaging(context.Background())
|
||
if err != nil {
|
||
log.Printf("Error getting Messaging client: %v", err)
|
||
}
|
||
|
||
message := &messaging.Message{
|
||
Token: DeviceToken,
|
||
Notification: &messaging.Notification{
|
||
Title: "Hello!",
|
||
Body: "This is a test push notification By Gowtham.",
|
||
},
|
||
}
|
||
|
||
response, err := client.Send(context.Background(), message)
|
||
if err != nil {
|
||
|
||
if err.Error() == "messaging/registration-token-not-registered" {
|
||
log.Println("⚠️ Device token is invalid. Remove it from the database.")
|
||
// Here you should remove the token from your database
|
||
} else {
|
||
log.Printf("Error sending message: %v", err)
|
||
}
|
||
} else {
|
||
fmt.Printf("✅ Successfully sent message: %s\n", response)
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, map[string]string{"status": "success"})
|
||
})
|
||
|
||
e.Router.POST("/api/getDataSet", func(c echo.Context) error {
|
||
|
||
type RequestData struct {
|
||
Dataset string `json:"dataset"`
|
||
KPI string `json:"kpi"`
|
||
Language string `json:"language"`
|
||
FilterData []utils.Filter `json:"filter_data"`
|
||
}
|
||
|
||
var requestData RequestData
|
||
|
||
// Bind JSON request to struct
|
||
if err := c.Bind(&requestData); err != nil {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request format"})
|
||
}
|
||
|
||
// Validate required fields
|
||
if requestData.Dataset == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "dataset is required"})
|
||
}
|
||
|
||
dataset := requestData.Dataset
|
||
language := requestData.Language
|
||
langKey := "value_" + language
|
||
|
||
// Fetch all matching records from charts table
|
||
param := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset})
|
||
records, err := app.Dao().FindRecordsByExpr("charts", param)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch records for dataset '%s': %v", dataset, err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"})
|
||
}
|
||
if len(records) == 0 {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for the given dataset"})
|
||
}
|
||
|
||
// Fetch all matching records from charts variables table
|
||
languageSource, err := app.Dao().FindRecordsByExpr("charts_variables", param)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch language source: %v", err)
|
||
}
|
||
// Convert languageSource to []map[string]interface{}
|
||
languageSourceConverted := []map[string]interface{}{}
|
||
for _, record := range languageSource {
|
||
languageSourceConverted = append(languageSourceConverted, record.SchemaData())
|
||
}
|
||
|
||
//construct chart and card data based of configuration/calculation/default
|
||
var aggregatedResponse []map[string]interface{}
|
||
for _, record := range records {
|
||
|
||
// Extract the URL for each record
|
||
apiURL := record.GetString("url")
|
||
file_name := record.GetString("kpi_file_name")
|
||
if apiURL == "" {
|
||
log.Printf("No URL for record '%v'", record)
|
||
continue // Skip records with no URL
|
||
}
|
||
|
||
// Fetch and convert XML to JSON
|
||
saveToFile := record.GetBool("file_status")
|
||
chartId := record.GetString("id")
|
||
if !saveToFile { // Check if saveToFile is false
|
||
_, err := fetchAndConvertXMLToJSON(apiURL, "kpi_files", file_name, "charts", chartId)
|
||
if err != nil {
|
||
log.Printf("Failed to process URL '%s': %v", apiURL, err)
|
||
continue // Skip this record and proceed with others
|
||
}
|
||
}
|
||
|
||
// Read the JSON data from the file
|
||
chartData, err := readJSONFromFile("kpi_files", file_name)
|
||
if err != nil {
|
||
log.Printf("Failed to read JSON for KPI '%s': %v", file_name, err)
|
||
}
|
||
|
||
// unset key and value if value to be excluded
|
||
keyToFilter := record.GetString("unset_key")
|
||
valueToExclude := record.GetString("unset_value")
|
||
if keyToFilter == "" || valueToExclude == "" {
|
||
// fmt.Println("unset_key or unset_value is empty. Skipping processing.")
|
||
} else {
|
||
// Filter the data dynamically and update chartData
|
||
chartData = filterDynamicChartData(chartData, keyToFilter, valueToExclude)
|
||
}
|
||
|
||
//logo url generating
|
||
var logoURL string
|
||
cardLogo := record.GetString("card_logo")
|
||
if cardLogo == "" {
|
||
logoURL = ""
|
||
} else {
|
||
logoURL = fmt.Sprintf("%s/api/files/charts/%s/%s",
|
||
baseUrl,
|
||
record.GetString("id"),
|
||
cardLogo,
|
||
)
|
||
}
|
||
|
||
cResRaw := utils.TranslateChartData(chartData, languageSourceConverted, langKey)
|
||
|
||
//responce data structure
|
||
chartHeadingKey := "chart_heading_" + language
|
||
chartSubHeadingKey := "chart_sub_heading_" + language
|
||
tabHeadingKey := "tab_heading_" + language
|
||
// Correct conditional assignment for "response"
|
||
recordResult := map[string]interface{}{
|
||
"url": apiURL,
|
||
"dataset": record.GetString("dataset"),
|
||
"main_id": record.GetString("main_id"),
|
||
"sub_id": record.GetString("sub_id"),
|
||
"kpi": record.GetString("kpi"),
|
||
"is_chart": record.GetString("is_chart"),
|
||
"chart_type": record.GetString("chart_type"),
|
||
"group_by": record.GetString("group_by"),
|
||
"chart_heading": record.GetString(chartHeadingKey),
|
||
"chart_sub_heading": record.GetString(chartSubHeadingKey),
|
||
"tab_heading": record.GetString(tabHeadingKey),
|
||
"unset_value": record.Get("unset_value"),
|
||
"card_logo": logoURL,
|
||
"order_id": record.Get("order_id"),
|
||
"card_type_json": record.Get("card_type_json"),
|
||
"tab_order": record.GetString("tab_order"),
|
||
"chart_type_json": record.Get("chart_type_json"),
|
||
"card_full_length": record.Get("card_full_length"),
|
||
"chart_bar_color": record.Get("chart_bar_color"),
|
||
"chart_height": record.Get("chart_height"),
|
||
}
|
||
|
||
var cRes []utils.Observation
|
||
cResJSON, err := json.Marshal(cResRaw)
|
||
if err != nil {
|
||
fmt.Println("Error encoding chart data:", err)
|
||
}
|
||
err = json.Unmarshal(cResJSON, &cRes)
|
||
if err != nil {
|
||
fmt.Println("Error decoding chart data:", err)
|
||
}
|
||
|
||
if requestData.KPI == record.GetString("kpi") && len(requestData.FilterData) > 0 {
|
||
|
||
filteredData, err := utils.FilterByMultipleKeys(cRes, requestData.FilterData)
|
||
if err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
if 0 < len(filteredData) {
|
||
cRes = filteredData
|
||
} else {
|
||
defaultYearsFilteredData, err := utils.DefaultYearFilter(cRes, record.GetString("default_latest_year_count"))
|
||
if err != nil {
|
||
fmt.Println("Error processing defaultYearsFilteredData:", err)
|
||
}
|
||
cRes = defaultYearsFilteredData
|
||
|
||
}
|
||
|
||
} else {
|
||
defaultYearsFilteredData, err := utils.DefaultYearFilter(cRes, record.GetString("default_latest_year_count"))
|
||
if err != nil {
|
||
fmt.Println("Error processing defaultYearsFilteredData:", err)
|
||
}
|
||
// fmt.Println("defaultYearsFilteredData:", defaultYearsFilteredData)
|
||
cRes = defaultYearsFilteredData
|
||
}
|
||
|
||
var cardResults []map[string]string
|
||
if record.GetString("is_chart") == "false" { // card data calculations
|
||
|
||
// Extract `card_type` JSON from `record.Get("card_type")`
|
||
requestData := record.Get("card_type_json")
|
||
requestJSON, err := json.Marshal(requestData) // Convert interface{} to JSON string
|
||
if err != nil {
|
||
fmt.Println("Error encoding request data:", err)
|
||
}
|
||
|
||
// Decode JSON into an array of `utils.Request`
|
||
var requests []utils.CardCalculationRequest
|
||
err = json.Unmarshal(requestJSON, &requests)
|
||
if err != nil {
|
||
fmt.Println("Error decoding request data:", err)
|
||
}
|
||
|
||
if record.GetString("custom_calculation") == "true" {
|
||
|
||
customCalculationReq := record.Get("chart_custom_calculation_json")
|
||
|
||
var chartCustomCalculationRequest utils.ChartCalculationRequest
|
||
|
||
jsonData, err := json.Marshal(customCalculationReq)
|
||
if err != nil {
|
||
log.Println("Error marshaling to JSON:", err)
|
||
}
|
||
|
||
err = json.Unmarshal(jsonData, &chartCustomCalculationRequest)
|
||
if err != nil {
|
||
log.Println("Error unmarshalling JSON:", err)
|
||
}
|
||
customResult, err := utils.CalculateGroupedSum(cRes, chartCustomCalculationRequest)
|
||
if err != nil {
|
||
fmt.Println("Error:", err)
|
||
}
|
||
|
||
cRes = customResult
|
||
|
||
}
|
||
|
||
// Process each request and store results
|
||
for _, req := range requests {
|
||
result, err := utils.CardCalculation(cRes, req)
|
||
if err != nil {
|
||
fmt.Println("Error processing data:", err)
|
||
continue // Skip this request and move to the next
|
||
}
|
||
cardResults = append(cardResults, result)
|
||
}
|
||
|
||
recordResult["response"] = cardResults
|
||
|
||
} else {
|
||
|
||
if record.GetString("custom_calculation") == "false" {
|
||
recordResult["response"] = cRes
|
||
} else {
|
||
|
||
// fmt.Println("cRes-------", cRes)
|
||
|
||
customCalculationReq := record.Get("chart_custom_calculation_json")
|
||
|
||
var chartCustomCalculationRequest utils.ChartCalculationRequest
|
||
|
||
jsonData, err := json.Marshal(customCalculationReq)
|
||
if err != nil {
|
||
log.Println("Error marshaling to JSON:", err)
|
||
}
|
||
|
||
err = json.Unmarshal(jsonData, &chartCustomCalculationRequest)
|
||
if err != nil {
|
||
log.Println("Error unmarshalling JSON:", err)
|
||
}
|
||
customResult, err := utils.CalculateGroupedSum(cRes, chartCustomCalculationRequest)
|
||
if err != nil {
|
||
fmt.Println("Error:", err)
|
||
}
|
||
|
||
recordResult["response"] = customResult
|
||
|
||
}
|
||
|
||
}
|
||
|
||
// Add the result to the aggregated response
|
||
aggregatedResponse = append(aggregatedResponse, recordResult)
|
||
|
||
}
|
||
//--------------------
|
||
|
||
// get all filter based on kpi
|
||
var dataSetKpis []struct {
|
||
Kpi string `db:"kpi" json:"kpi"`
|
||
}
|
||
err = app.DB().Select("kpi").From("charts").Where(dbx.HashExp{"dataset": dataset}).GroupBy("kpi").All(&dataSetKpis)
|
||
if err != nil {
|
||
log.Fatalf("Failed to get kpi groupby data: %v", err)
|
||
}
|
||
|
||
aggregatedFilterResponse := make(map[string]interface{})
|
||
for _, dataSetKpi := range dataSetKpis {
|
||
|
||
kpi := dataSetKpi.Kpi
|
||
filterData, err := fetchfilterJSON(kpi)
|
||
|
||
// Translate filter data using the desired dataset and language key
|
||
fRes, err := TranslateFilters(app, dataset, filterData, langKey)
|
||
if err != nil {
|
||
log.Fatalf("Error translating filters: %v", err)
|
||
}
|
||
|
||
aggregatedFilterResponse[kpi] = fRes
|
||
|
||
}
|
||
//--------------------
|
||
|
||
//mainTopic kpi heading and screen color
|
||
headingAndColor := struct {
|
||
DataSet string `db:"data_set" json:"data_set"`
|
||
MainTopic string `db:"main_topic" json:"main_topic"`
|
||
SubTopic string `db:"sub_topic" json:"sub_topic"`
|
||
DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"`
|
||
HeaderColor string `db:"header_color" json:"header_color"`
|
||
BodyColor string `db:"body_color" json:"body_color"`
|
||
BorderColor string `db:"border_color" json:"border_color"`
|
||
}{}
|
||
|
||
data_set_tile_heading := "data_set_tile_heading_" + language
|
||
main_topic := "main_topic_" + language
|
||
sub_topic := "sub_topic_" + language
|
||
query := ` SELECT id,data_set,` + data_set_tile_heading + ` AS data_set_tile_heading,` + main_topic + ` AS main_topic,` + sub_topic + ` AS sub_topic,header_color,body_color,border_color FROM uae_numbers_screen WHERE data_set = {:data_set} `
|
||
err = app.DB().NewQuery(query).Bind(dbx.Params{"data_set": dataset}).One(&headingAndColor)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch data-set for sub topic %s: %v", dataset, err)
|
||
}
|
||
//---------------------
|
||
|
||
// Create the response structure
|
||
response := map[string]interface{}{
|
||
"new_filter_data": aggregatedFilterResponse,
|
||
"data": aggregatedResponse,
|
||
"screen_heading_and_color": headingAndColor,
|
||
"kpi": requestData.KPI,
|
||
"filter_data": requestData.FilterData,
|
||
}
|
||
|
||
if len(aggregatedResponse) == 0 {
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "No data could be fetched from the provided URLs"})
|
||
}
|
||
|
||
return c.JSON(http.StatusOK, response)
|
||
})
|
||
|
||
e.Router.GET("/api/getHomePageData", func(c echo.Context) error {
|
||
|
||
language := c.QueryParam("language")
|
||
|
||
// Define the struct for holding the query results
|
||
mainTopics := []struct {
|
||
MainTopicEn string `db:"main_topic_en" json:"main_topic_en"`
|
||
MainTopicAr string `db:"main_topic_ar" json:"main_topic_ar"`
|
||
MainTopicListOrder string `db:"main_topic_list_order" json:"main_topic_list_order"`
|
||
ColorPattern string `db:"color_pattern" json:"color_pattern"`
|
||
}{}
|
||
|
||
// Execute the query
|
||
err := app.DB().
|
||
Select("main_topic_en", "main_topic_ar", "main_topic_list_order", "color_pattern").
|
||
From("home_screen").
|
||
GroupBy("main_topic_en", "main_topic_ar", "color_pattern").
|
||
OrderBy("main_topic_list_order ASC").
|
||
All(&mainTopics)
|
||
|
||
if err != nil {
|
||
log.Printf("Failed to fetch home_screen data: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"})
|
||
}
|
||
|
||
// If no records are found
|
||
if len(mainTopics) == 0 {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for home screen"})
|
||
}
|
||
|
||
// Final result structure
|
||
result := []map[string]interface{}{}
|
||
|
||
// Loop through main topics and fetch sub-topic data for each
|
||
for _, mainTopic := range mainTopics {
|
||
|
||
dataSets := []struct {
|
||
DataSet string `db:"data_set" json:"data_set"`
|
||
DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"`
|
||
ValueSource string `db:"value_source" json:"value_source"`
|
||
Value string `db:"value" json:"value"`
|
||
DataSetListOrder string `db:"data_set_list_order" json:"data_set_list_order"`
|
||
}{}
|
||
|
||
languageSpecificColumn := "data_set_tile_heading_" + language
|
||
query := ` SELECT data_set,` + languageSpecificColumn + ` AS data_set_tile_heading,value_source,value, data_set_list_order FROM home_screen WHERE main_topic_en = {:topic}`
|
||
err := app.DB().
|
||
NewQuery(query).
|
||
Bind(dbx.Params{
|
||
"topic": mainTopic.MainTopicEn,
|
||
}).
|
||
All(&dataSets)
|
||
|
||
if err != nil {
|
||
log.Printf("Error fetching data sets: %v", err)
|
||
continue
|
||
}
|
||
|
||
var MainTopic string
|
||
if language == "en" {
|
||
MainTopic = mainTopic.MainTopicEn
|
||
} else {
|
||
MainTopic = mainTopic.MainTopicAr
|
||
}
|
||
|
||
// Add the main topic and its sub-topics to the result
|
||
result = append(result, map[string]interface{}{
|
||
"main_topic": MainTopic,
|
||
"main_topic_list_order": mainTopic.MainTopicListOrder,
|
||
"color_pattern": mainTopic.ColorPattern,
|
||
"tile_data": dataSets,
|
||
})
|
||
}
|
||
|
||
// Send the combined result as JSON
|
||
return c.JSON(http.StatusOK, result)
|
||
|
||
})
|
||
|
||
e.Router.GET("/api/getUAENumbersData", func(c echo.Context) error {
|
||
|
||
language := c.QueryParam("language")
|
||
|
||
// Define the struct for holding the query results
|
||
mainTopics := []struct {
|
||
MainTopicEn string `db:"main_topic_en" json:"main_topic_en"`
|
||
MainTopicAr string `db:"main_topic_ar" json:"main_topic_ar"`
|
||
MainTopicListOrder string `db:"main_topic_list_order" json:"main_topic_list_order"`
|
||
ColorPattern string `db:"color_pattern" json:"color_pattern"`
|
||
}{}
|
||
|
||
// Execute the query
|
||
err := app.DB().
|
||
Select("main_topic_en", "main_topic_ar", "main_topic_list_order", "color_pattern").
|
||
From("uae_numbers_screen").
|
||
GroupBy("main_topic_en", "main_topic_ar", "color_pattern").
|
||
OrderBy("main_topic_list_order ASC").
|
||
All(&mainTopics)
|
||
|
||
if err != nil {
|
||
log.Printf("Failed to fetch uae_numbers_screen data: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"})
|
||
}
|
||
|
||
// If no records are found
|
||
if len(mainTopics) == 0 {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for home screen"})
|
||
}
|
||
|
||
// Final result structure
|
||
result := []map[string]interface{}{}
|
||
|
||
// Loop through main topics and fetch sub-topic data for each
|
||
for _, mainTopic := range mainTopics {
|
||
|
||
subTopics := []struct {
|
||
SubTopicEn string `db:"sub_topic_en" json:"sub_topic_en"`
|
||
SubTopicAr string `db:"sub_topic_ar" json:"sub_topic_ar"`
|
||
SubTopicListOrder string `db:"sub_topic_list_order" json:"sub_topic_list_order"`
|
||
}{}
|
||
|
||
// Query to get sub-topics for the current main topic
|
||
err := app.DB().
|
||
Select("sub_topic_en", "sub_topic_ar", "sub_topic_list_order").
|
||
From("uae_numbers_screen").
|
||
Where(dbx.HashExp{"main_topic_en": mainTopic.MainTopicEn}).
|
||
GroupBy("sub_topic_en", "sub_topic_ar").
|
||
OrderBy("sub_topic_list_order ASC").
|
||
All(&subTopics)
|
||
|
||
if err != nil {
|
||
log.Printf("Failed to fetch sub-topics for main topic %s: %v", mainTopic.MainTopicEn, err)
|
||
continue
|
||
}
|
||
|
||
result2 := []map[string]interface{}{}
|
||
// Loop through sub Topics topics and fetch dataSet data for each
|
||
for _, subTopic := range subTopics {
|
||
|
||
dataSets := []struct {
|
||
DataSet string `db:"data_set" json:"data_set"`
|
||
DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"`
|
||
ValueSource string `db:"value_source" json:"value_source"`
|
||
Value string `db:"value" json:"value"`
|
||
DataSetListOrder string `db:"data_set_list_order" json:"data_set_list_order"`
|
||
}{}
|
||
|
||
languageSpecificColumn := "data_set_tile_heading_" + language
|
||
query := ` SELECT data_set,` + languageSpecificColumn + ` AS data_set_tile_heading,value_source,value, data_set_list_order FROM uae_numbers_screen WHERE sub_topic_en = {:topic}`
|
||
err := app.DB().
|
||
NewQuery(query).
|
||
Bind(dbx.Params{
|
||
"topic": subTopic.SubTopicEn,
|
||
}).
|
||
All(&dataSets)
|
||
|
||
// languageSpecificColumn := "data_set_tile_heading_" + language
|
||
// // Query to get data-set for the current sub topic
|
||
// err := app.DB().
|
||
// Select("data_set", languageSpecificColumn, "value_source", "value", "data_set_list_order").
|
||
// From("uae_numbers_screen").
|
||
// Where(dbx.HashExp{"sub_topic_en": subTopic.SubTopicEn}).
|
||
// OrderBy("data_set_list_order ASC").
|
||
// All(&dataSets)
|
||
|
||
if err != nil {
|
||
log.Printf("Failed to fetch data-set for sub topic %s: %v", subTopic.SubTopicEn, err)
|
||
continue
|
||
}
|
||
|
||
var SubTopic string
|
||
if language == "en" {
|
||
SubTopic = subTopic.SubTopicEn
|
||
} else {
|
||
SubTopic = subTopic.SubTopicAr
|
||
}
|
||
|
||
result2 = append(result2, map[string]interface{}{
|
||
"sub_topic": SubTopic,
|
||
"sub_topic_list_order": subTopic.SubTopicListOrder,
|
||
"tile_data": dataSets,
|
||
})
|
||
}
|
||
|
||
var MainTopic string
|
||
if language == "en" {
|
||
MainTopic = mainTopic.MainTopicEn
|
||
} else {
|
||
MainTopic = mainTopic.MainTopicAr
|
||
}
|
||
|
||
// Add the main topic and its sub-topics to the result
|
||
result = append(result, map[string]interface{}{
|
||
"main_topic": MainTopic,
|
||
"main_topic_list_order": mainTopic.MainTopicListOrder,
|
||
"color_pattern": mainTopic.ColorPattern,
|
||
"sub_topics": result2,
|
||
})
|
||
}
|
||
|
||
// Send the combined result as JSON
|
||
return c.JSON(http.StatusOK, result)
|
||
|
||
})
|
||
|
||
e.Router.GET("/api/getUserBookmark", func(c echo.Context) error {
|
||
|
||
language := c.QueryParam("language")
|
||
user_id := c.QueryParam("user_id")
|
||
|
||
// Define the struct for holding the query results
|
||
bookmarks := []struct {
|
||
Id string `db:"id" json:"id"`
|
||
UserId string `db:"user_id" json:"user_id"`
|
||
Dataset string `db:"dataset" json:"dataset"`
|
||
}{}
|
||
|
||
// Execute the query
|
||
err := app.DB().
|
||
Select("id", "user_id", "dataset").
|
||
From("bookmark").
|
||
Where(dbx.HashExp{"user_id": user_id}).
|
||
All(&bookmarks)
|
||
// return c.JSON(http.StatusOK, bookmarks)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch bookmark data: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"})
|
||
}
|
||
|
||
// If no records are found
|
||
if len(bookmarks) == 0 {
|
||
return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for Bookmark"})
|
||
}
|
||
|
||
// Final result structure
|
||
result := []map[string]interface{}{}
|
||
|
||
// Loop through main topics and fetch sub-topic data for each
|
||
for _, bookmark := range bookmarks {
|
||
dataSets := struct {
|
||
DataSet string `db:"data_set" json:"data_set"`
|
||
MainTopic string `db:"main_topic" json:"main_topic"`
|
||
SubTopic string `db:"sub_topic" json:"sub_topic"`
|
||
DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"`
|
||
ValueSource string `db:"value_source" json:"value_source"`
|
||
Value string `db:"value" json:"value"`
|
||
ColorPattern string `db:"color_pattern" json:"color_pattern"`
|
||
}{}
|
||
|
||
data_set_tile_heading := "data_set_tile_heading_" + language
|
||
main_topic := "main_topic_" + language
|
||
sub_topic := "sub_topic_" + language
|
||
query := ` SELECT id,data_set,` + data_set_tile_heading + ` AS data_set_tile_heading,` + main_topic + ` AS main_topic,` + sub_topic + ` AS sub_topic,color_pattern,value_source,value FROM uae_numbers_screen WHERE data_set = {:bookmark} `
|
||
err := app.DB().
|
||
NewQuery(query).
|
||
Bind(dbx.Params{
|
||
"bookmark": bookmark.Dataset,
|
||
}).
|
||
One(&dataSets)
|
||
|
||
log.Printf("query %s: %v", bookmark.Dataset, query)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch data-set for sub topic %s: %v", bookmark.Dataset, err)
|
||
continue
|
||
}
|
||
|
||
// Add the main topic and its sub-topics to the result
|
||
result = append(result, map[string]interface{}{
|
||
"id": bookmark.Id,
|
||
"data_set": dataSets.DataSet,
|
||
"data_set_tile_heading": dataSets.DataSetTileHeading,
|
||
"value_source": dataSets.ValueSource,
|
||
"value": dataSets.Value,
|
||
"main_topic": dataSets.MainTopic,
|
||
"sub_topic": dataSets.SubTopic,
|
||
"color_pattern": dataSets.ColorPattern,
|
||
})
|
||
|
||
}
|
||
|
||
// Send the combined result as JSON
|
||
return c.JSON(http.StatusOK, result)
|
||
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
//send mail while status changed from app admin
|
||
app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error {
|
||
if e.Collection.Name == "users" {
|
||
originalRecord, err := app.Dao().FindRecordById("users", e.Record.GetString("id"))
|
||
if err != nil {
|
||
return fmt.Errorf("failed to fetch original record: %v", err)
|
||
}
|
||
|
||
oldStatus := originalRecord.GetString("status")
|
||
newStatus := e.Record.GetString("status")
|
||
|
||
// Check if the status has changed
|
||
if oldStatus != newStatus {
|
||
err := sendStatusChangeEmail(app, e.Record, newStatus)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
})
|
||
|
||
//Hook to send a verification link to register user
|
||
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||
if e.Record.Collection().Name == "users" {
|
||
// Retrieve user details
|
||
name := e.Record.GetString("uname")
|
||
email := e.Record.GetString("email")
|
||
userID := e.Record.Id // User ID
|
||
|
||
imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo")
|
||
if err != nil {
|
||
log.Printf("Failed to fetch logo from images collection: %v", err)
|
||
return nil
|
||
}
|
||
|
||
// Construct the logo URL
|
||
logoURL := fmt.Sprintf("%s/api/files/images/%s/%s",
|
||
baseUrl,
|
||
imageRecord.Id,
|
||
imageRecord.GetString("file"),
|
||
)
|
||
|
||
// Email subject
|
||
subject := "FCSC Verify Your Email to Complete Registration"
|
||
|
||
// Generate a verification URL (example: using user ID or token)
|
||
verificationURL := fmt.Sprintf("https://pb.venbait.in/verify-email?userId=%s", userID)
|
||
|
||
// Email body
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering with FCSC. To complete your registration and activate your account, please verify your email address by clicking the link below: </p>
|
||
<p>
|
||
<a href="%s" style="color: blue; text-decoration: underline;">Verify Email</a>
|
||
</p>
|
||
<p>If you did not register for this account, please ignore this email.</p>
|
||
</br>
|
||
<p>Best regards,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
</body>
|
||
</html>
|
||
`, name, verificationURL, logoURL)
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send verification email to user: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Verification email sent successfully to the user.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
// Hook to send an email after a user is created
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
e.Router.GET("/api/custom/approve", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId")
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("reviewed") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Your Already Reviewed</h1>
|
||
<p>You have already been reviewed. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Update the status
|
||
newStatus := "Approved"
|
||
record.Set("verified", true)
|
||
record.Set("status", newStatus)
|
||
record.Set("reviewed", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Send the status change email
|
||
err = sendStatusChangeEmail(app, record, newStatus)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send approval email.",
|
||
})
|
||
}
|
||
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #e0f7fa;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #00796b;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #004d40;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #00796b;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<i>✔</i>
|
||
<h1>User Approved Successfully</h1>
|
||
<p>The user has been approved, and the approval email has been sent.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
// Approve Endpoint
|
||
e.Router.GET("/api/custom/reject", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId")
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("reviewed") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Your Already Reviewed</h1>
|
||
<p>You have already been reviewed. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Check if the status is already "Denied" to avoid duplicate email
|
||
if record.GetString("status") == "Denied" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "User is already rejected.",
|
||
})
|
||
}
|
||
|
||
// Update the status
|
||
newStatus := "Denied"
|
||
record.Set("verified", true)
|
||
record.Set("status", newStatus)
|
||
record.Set("reviewed", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Send the status change email
|
||
err = sendStatusChangeEmail(app, record, newStatus)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send rejection email.",
|
||
})
|
||
}
|
||
|
||
// Render a success message as an HTML response
|
||
return c.HTML(http.StatusOK, `
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>User Rejected</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100vh;
|
||
background-color: #ffe6e6;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
.message-box {
|
||
text-align: center;
|
||
color: #d9534f;
|
||
}
|
||
.message-box i {
|
||
font-size: 48px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.message-box p {
|
||
font-size: 18px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-box">
|
||
<i>❌</i>
|
||
<h1>User Rejected</h1>
|
||
<p>Rejection email sent successfully.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
// Serve static files from the provided public directory (if exists)
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
e.Router.GET("/verify-email", verifyEmailHandler)
|
||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false))
|
||
return nil
|
||
})
|
||
|
||
//Feedback Mail Trigger
|
||
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||
if e.Record.Collection().Name == "feedback" {
|
||
// Retrieve feedback details
|
||
feedback := e.Record
|
||
userId := feedback.GetString("userId")
|
||
emojiRating := feedback.GetString("emoji_rating")
|
||
easeOfUse := feedback.GetString("ease_of_use")
|
||
quality := feedback.GetString("quality")
|
||
design := feedback.GetString("design")
|
||
redundancy := feedback.GetString("redundancy")
|
||
additionalFeedback := feedback.GetString("feedback")
|
||
|
||
if additionalFeedback == "" {
|
||
additionalFeedback = "No additional feedback provided."
|
||
}
|
||
|
||
// Fetch user details using userId
|
||
user, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
log.Printf("Error fetching user details: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
userName := user.GetString("uname")
|
||
|
||
// Extract and format the creation time
|
||
createdTime := e.Record.Get("created").(types.DateTime) // Extract created time
|
||
feedbackDateTime := createdTime.Time().UTC() // Convert to time.Time in UTC
|
||
|
||
// Format the date and time
|
||
formattedDate := feedbackDateTime.Format("02-01-2006") // DD-MM-YYYY
|
||
formattedDateTime := feedbackDateTime.Format("02.01.2006 15:04 UTC") // DD.MM.YYYY HH:mm UTC
|
||
|
||
// Admin details
|
||
adminName := "FCSC"
|
||
|
||
// Email subject
|
||
subject := "UAE Stats Feedback"
|
||
|
||
imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo")
|
||
if err != nil {
|
||
log.Printf("Failed to fetch logo from images collection: %v", err)
|
||
return nil
|
||
}
|
||
|
||
// Construct the logo URL
|
||
logoURL := fmt.Sprintf("%s/api/files/images/%s/%s",
|
||
baseUrl,
|
||
imageRecord.Id,
|
||
imageRecord.GetString("file"),
|
||
)
|
||
|
||
// Email body in HTML format
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p> You have received new feedback from a user through the mobile application.</p>
|
||
<p><b>User Details:</b></p>
|
||
<ul>
|
||
<li><b>Name</b> %s</li>
|
||
<li><b>Date of Submission:</b> %s</li>
|
||
<li><b>Time of Submission:</b> %s</li>
|
||
</ul>
|
||
<p><b>Feedback:</b></p>
|
||
<pre>
|
||
1. How was your experience with us today? Rating: %s
|
||
2. How did we perform in key areas?
|
||
1. Ease of Use: %s
|
||
2. Quality: %s
|
||
3. Design: %s
|
||
4. Redundancy: %s
|
||
3. Additional Feedback:
|
||
1. %s
|
||
</pre>
|
||
<p>Thank you,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
</body>
|
||
</html>
|
||
`, adminName, userName, formattedDate, formattedDateTime, emojiRating, easeOfUse, quality, design, redundancy, additionalFeedback, logoURL)
|
||
|
||
// Define the target type (e.g., "smtp")
|
||
targetType := "feedback_receive_mail"
|
||
|
||
// Fetch the email configuration record where type matches targetType
|
||
emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", targetType)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch email configuration for type '%s': %v", targetType, err)
|
||
return fmt.Errorf("email configuration not found for type '%s'", targetType)
|
||
}
|
||
|
||
// Get the email address
|
||
feedbackEmail := emailConfig.GetString("email")
|
||
if feedbackEmail == "" {
|
||
log.Printf("No email address configured for type '%s'", targetType)
|
||
return fmt.Errorf("no email address configured for type '%s'", targetType)
|
||
}
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: adminName,
|
||
Address: feedbackEmail,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send feedback email to admin: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Feedback email sent successfully to the admin.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
app.OnRecordAfterCreateRequest("otp_requests").Add(func(e *core.RecordCreateEvent) error {
|
||
// Ensure the record has an email field
|
||
email, ok := e.Record.Get("email").(string)
|
||
if !ok || email == "" {
|
||
log.Println("Invalid or missing email field.")
|
||
return nil // or handle this case appropriately
|
||
}
|
||
|
||
// Generate the OTP
|
||
otp, err := generateOTP()
|
||
if err != nil {
|
||
log.Printf("Failed to generate OTP: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Calculate the expiration time (5 minutes from now)
|
||
expiresAt := time.Now().Add(5 * time.Minute)
|
||
|
||
// Create a mail.Address for the sender
|
||
sender := mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress, // Ensure this is a valid email
|
||
}
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: sender,
|
||
To: []mail.Address{{Address: email}}, // Wrap the recipient email
|
||
Subject: "YOUR VERIFICATION CODE",
|
||
HTML: fmt.Sprintf(
|
||
`<p>Thanks for verifying your <strong>%s</strong> account!</p>
|
||
<p>Your code is: <strong>%d</strong></p>
|
||
<p>Sincerely,<br>Support team.</p>`,
|
||
email, otp,
|
||
),
|
||
}
|
||
|
||
// Send the email
|
||
if err := app.NewMailClient().Send(message); err != nil {
|
||
log.Printf("Failed to send email: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Update the record with the OTP
|
||
e.Record.Set("otp", fmt.Sprintf("%04d", otp))
|
||
e.Record.Set("expires_at", expiresAt) // Set the expiration time
|
||
|
||
if err := app.Dao().SaveRecord(e.Record); err != nil {
|
||
log.Printf("Failed to update record with OTP and expiration time: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
log.Printf("OTP sent to %s expires at and saved successfully.\n", email)
|
||
return nil
|
||
})
|
||
|
||
// Start the PocketBase server
|
||
go func() {
|
||
if err := app.Start(); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}()
|
||
|
||
// Start your custom HTTP server on port 8091
|
||
log.Println("Starting custom HTTP server on :8091")
|
||
if err := http.ListenAndServe(":8091", nil); err != nil {
|
||
log.Fatal("Failed to start server: ", err)
|
||
}
|
||
}
|
||
|
||
const baseUrl = "https://pb.venbait.in"
|
||
|
||
// generateOTP generates a random 4-digit OTP
|
||
func generateOTP() (int, error) {
|
||
max := big.NewInt(10000) // 4-digit numbers range: 0000 to 9999
|
||
n, err := rand.Int(rand.Reader, max)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return int(n.Int64()), nil
|
||
}
|
||
|
||
// Email verification handler
|
||
func verifyEmailHandler(c echo.Context) error {
|
||
log.Println("Starting verifyEmailHandler")
|
||
|
||
// Get the userId from the query parameter
|
||
userID := c.QueryParam("userId")
|
||
|
||
if userID == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{
|
||
"message": "User ID not provided",
|
||
})
|
||
}
|
||
|
||
// Check if app is properly initialized
|
||
if app == nil {
|
||
log.Fatal("PocketBase app is not initialized")
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||
"message": "Internal server error",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userID)
|
||
if err != nil {
|
||
log.Printf("Error finding user: %v", err)
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("user_mail_verify") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Email Already Verified</h1>
|
||
<p>Your email has already been verified. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Set the verified status to true
|
||
record.Set("user_mail_verify", true)
|
||
// log.Printf("Failed to verify user: %v", err)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
log.Printf("Failed to update email verification status: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||
"error": "Failed to update email verification status",
|
||
})
|
||
}
|
||
|
||
response := c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #e0f7fa;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #00796b;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #004d40;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #00796b;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✔</div>
|
||
<h1>Your registration is pending for Admin Approval</h1>
|
||
<p>Access will be granted once your account is approved.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
|
||
// Fetch user details for email notification
|
||
username := record.GetString("uname")
|
||
userEmail := record.GetString("email")
|
||
|
||
// Send admin email asynchronously
|
||
go func() {
|
||
err = sendAdminEmail(app, userID, username, userEmail)
|
||
if err != nil {
|
||
log.Printf("Failed to send admin email: %v", err)
|
||
}
|
||
}()
|
||
|
||
return response
|
||
|
||
}
|
||
|
||
func sendAdminEmail(app *pocketbase.PocketBase, userID, username, userEmail string) error {
|
||
adminName := "Admin"
|
||
subject := "New User Registration Pending Review"
|
||
|
||
// Fetch the email configuration from PocketBase
|
||
emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", "admin_receive_mail")
|
||
if err != nil {
|
||
log.Printf("Failed to fetch email configuration: %v", err)
|
||
return err
|
||
}
|
||
adminEmail := emailConfig.GetString("email")
|
||
|
||
// Construct email body
|
||
body := fmt.Sprintf(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Registration Update – UAE Stats App</title>
|
||
</head>
|
||
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f8f8f8;">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" align="center">
|
||
<tr>
|
||
<td align="center" style="padding: 20px;">
|
||
<table role="presentation" width="600px" cellspacing="0" cellpadding="0" border="0" style="background-color: #ffffff; border: 2px solid #d1ad5c; padding: 20px;">
|
||
<tr>
|
||
<td align="center" style="font-size: 18px; font-weight: bold;">
|
||
<span style=" font-size:16px; border-bottom:1px solid #d1ad5c">
|
||
تسجيل مستخدم جديد - تطبيق إحصاءات الإمارات العربية المتحدة - قيد المراجعة<br>
|
||
</span>
|
||
<br><span style="font-size: 18px;">New User Registration - UAE Stats App - Pending Review</span>
|
||
<br><br><br>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; font-size: 12px; padding-bottom:30px">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0">
|
||
<tr>
|
||
<td align="left" style="width: 56%%; vertical-align: top;">
|
||
<strong>Dear <span style="color: #d1ad5c;">%s</span>,</strong><br><br>
|
||
A new user has registered on UAE Stats Mobile app and is awaiting your review.
|
||
<br><br>
|
||
<strong>Details:</strong><br>
|
||
Name: %s<br>
|
||
Email: <a href="mailto:%s" style="color: #d1ad5c; text-decoration: none;">%s</a>
|
||
<br><br>
|
||
Please review the registration and take appropriate action.
|
||
<br><br>
|
||
<a href="https://pb.venbait.in/api/custom/approve?userId=%s" style="background-color: green; color: white; padding: 5px 10px; border: none; cursor: pointer; border-radius:4px; font-size: 10px;">Approve ✅</a>
|
||
<a href="https://pb.venbait.in/api/custom/reject?userId=%s" style="background-color: red; color: white; padding: 5px 10px; border: none; cursor: pointer; border-radius:4px; font-size: 10px;">Reject ❌</a>
|
||
</td>
|
||
<td align="right" style="width: 42%%; vertical-align: top; direction: rtl; text-align: right;">
|
||
<strong>عزيزي <span style="color: #d1ad5c;">%s</span>،</strong><br><br>
|
||
تم تسجيل مستخدم جديد في تطبيق إحصاءات الإمارات وهو في انتظار مراجعتك.
|
||
<br><br>
|
||
<strong>التفاصيل:</strong><br>
|
||
الاسم: %s<br>
|
||
البريد الإلكتروني: <a href="mailto:%s" style="color: #d1ad5c; text-decoration: none;">%s</a>
|
||
<br><br>
|
||
يرجى مراجعة التسجيل واتخاذ الإجراء المناسب.
|
||
<br><br>
|
||
<a href="https://pb.venbait.in/api/custom/reject?userId=%s" style="background-color: red; color: white; padding: 5px 10px; border: none; cursor: pointer; border-radius:4px; font-size: 10px;">❌ Reject</a>
|
||
<a href="https://pb.venbait.in/api/custom/approve?userId=%s" style="background-color: green; color: white; padding: 5px 10px; border: none; cursor: pointer; border-radius:4px; font-size: 10px;">✅ Approve</a>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center" padding-top: 20px;">
|
||
<img src="https://imageats.s3.ap-south-1.amazonaws.com/signature.png" alt="We The UAE 2031" width="100%%">
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; font-size: 12px; color: #777; border-top: 1px solid #ccc;">
|
||
<strong>تنويه:</strong> المعلومات الواردة في هذه الرسالة الإلكترونية والمرفقات الخاصة بها سرية وقد تكون محمية بموجب القانون. إذا لم تكن الشخص المعني باستلام هذه الرسالة، فيرجى إخطار المرسل وحذف هذه الرسالة فوراً.
|
||
<br><br>
|
||
<strong>Disclaimer:</strong> The information contained and transmitted within this email message is PRIVILEGED AND/OR CONFIDENTIAL. If you are not the intended recipient, please notify the sender and delete this email immediately.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`,
|
||
adminName, username, userEmail, userEmail, userID, userID,
|
||
adminName, username, userEmail, userEmail, userID, userID)
|
||
|
||
// Send email
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{Name: adminName, Address: adminEmail},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
if err := app.NewMailClient().Send(message); err != nil {
|
||
log.Printf("Failed to send admin email: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Admin email sent successfully.")
|
||
return nil
|
||
}
|
||
|
||
func sendStatusChangeEmail(app *pocketbase.PocketBase, record *models.Record, newStatus string) error {
|
||
userEmail := record.GetString("email")
|
||
userName := record.GetString("uname")
|
||
log.Printf("userName: %s", userName)
|
||
|
||
if userEmail == "" {
|
||
return fmt.Errorf("user email is empty")
|
||
}
|
||
|
||
// imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo")
|
||
// if err != nil {
|
||
// log.Printf("Failed to fetch logo from images collection: %v", err)
|
||
// return nil
|
||
// }
|
||
|
||
// // Construct the logo URL
|
||
// logoURL := fmt.Sprintf("%s/api/files/images/%s/%s",
|
||
// baseUrl,
|
||
// imageRecord.Id,
|
||
// imageRecord.GetString("file"),
|
||
// )
|
||
|
||
var subject, body string
|
||
|
||
if newStatus == "Approved" {
|
||
subject = "Registration Approved – FCSC"
|
||
body = fmt.Sprintf(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Registration Update – UAE Stats App</title>
|
||
</head>
|
||
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f8f8f8;">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" align="center">
|
||
<tr>
|
||
<td align="center" style="padding: 20px;">
|
||
<table role="presentation" width="600px" cellspacing="0" cellpadding="0" border="0" style="background-color: #ffffff; border: 2px solid #d1ad5c; padding: 20px;">
|
||
<tr>
|
||
|
||
<tr>
|
||
<td align="center" style="font-size: 18px; font-weight: bold;">
|
||
<span style=" font-size:18px; border-bottom:1px solid #d1ad5c">
|
||
تمت الموافقة على التسجيل - إحصاءات الإمارات العربية المتحدة<br>
|
||
</span><br><span style="font-size: 18px;">Registration Approved – UAE Statistics App</span>
|
||
<br><br><br>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; font-size: 12px;">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0">
|
||
<tr>
|
||
<td align="left" style="width: 56%%; vertical-align: top;">
|
||
<strong>Dear <span style="color: #d1ad5c;">%s</span>,</strong><br><br>
|
||
We are pleased to inform you that your registration with UAE Stats app has been approved. You can now log in using the credentials that you had created.
|
||
<br><br>
|
||
Please access your account using the following link: <a href="https://www.fcsc.com/login" style="color: #d1ad5c; text-decoration: none;">Login</a>.
|
||
<br><br>
|
||
If you have any questions or believe this decision was made in error, please feel free to contact us for further clarification.
|
||
</td>
|
||
<td align="right" style="width: 42%%; vertical-align: top; direction: rtl; padding-bottom:30px">
|
||
<strong>عزيزي <span style="color: #d1ad5c;">%s</span>،</strong><br><br>
|
||
يسرنا إبلاغك بأنه تمت الموافقة على تسجيلك في تطبيق إحصاءات الإمارات. يمكنك الآن تسجيل الدخول باستخدام بيانات الاعتماد التي أنشأتها.
|
||
<br><br>
|
||
يرجى الوصول إلى حسابك عبر الرابط التالي <a href="#" style="color: #d1ad5c; text-decoration: none;">[URL]</a>.
|
||
<br><br>
|
||
إذا كانت لديك أي استفسارات أو كنت تعتقد أن هذا القرار خاطئ، فلا تتردد في التواصل معنا لمزيد من التوضيح.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center" padding-top: 20px;">
|
||
<img src="https://imageats.s3.ap-south-1.amazonaws.com/signature.png" alt="We The UAE 2031" width="100%">
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; font-size: 12px; color: #777; border-top: 1px solid #ccc;">
|
||
<strong>تنويه:</strong> المعلومات الواردة في هذه الرسالة الإلكترونية والمرفقات الخاصة بها سرية وقد تكون محمية بموجب القانون. إذا لم تكن الشخص المعني باستلام هذه الرسالة، فيرجى إخطار المرسل وحذف هذه الرسالة فوراً.
|
||
<br><br>
|
||
<strong>Disclaimer:</strong> The information contained and transmitted within this email message is PRIVILEGED AND/OR CONFIDENTIAL. If you are not the intended recipient, please notify the sender and delete this email immediately.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`, userName, userName)
|
||
} else if newStatus == "Denied" {
|
||
subject = "Registration Update – FCSC"
|
||
body = fmt.Sprintf(`
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Registration Update – UAE Stats App</title>
|
||
</head>
|
||
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f8f8f8;">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0" align="center">
|
||
<tr>
|
||
<td align="center" style="padding: 20px;">
|
||
<table role="presentation" width="600px" cellspacing="0" cellpadding="0" border="0" style="background-color: #ffffff; border: 2px solid #d1ad5c; padding: 20px;">
|
||
<tr>
|
||
<td align="center" style="font-size: 18px; font-weight: bold;">
|
||
<span style=" font-size:16px; border-bottom:1px solid #d1ad5c">
|
||
تحديث التسجيل – تطبيق إحصاءات الإمارات العربية المتحدة<br>
|
||
</span><br><span style="font-size: 18px;">Registration Update – UAE Stats App</span>
|
||
<br><br><br>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; padding-bottom:30px; font-size: 12px;">
|
||
<table role="presentation" width="100%%" cellspacing="0" cellpadding="0" border="0">
|
||
<tr>
|
||
<td align="left" style="width: 56%%; vertical-align: top;">
|
||
<strong>Dear <span style="color: #d1ad5c;">%s</span>,</strong><br><br>
|
||
Thank you for registering with UAE Stats Mobile App. After careful review, we regret to inform you that your registration has not been approved at this time.
|
||
<br><br>
|
||
If you have any questions or believe this decision was made in error, please feel free to <a href="#" style="color: #d1ad5c; text-decoration: none; font-weight: bold;">contact us</a> for further clarification.
|
||
<br><br>
|
||
We appreciate your understanding and thank you for your interest.
|
||
</td>
|
||
<td align="right" style="width: 42%%; vertical-align: top; direction: rtl;">
|
||
<strong>عزيزي <span style="color: #d1ad5c;">%s</span>،</strong><br><br>
|
||
نشكرك على تسجيلك في تطبيق إحصاءات الإمارات. بعد مراجعة دقيقة، نأسف لإبلاغك بأن تسجيلك لم يتم الموافقة عليه في هذا الوقت.
|
||
<br><br>
|
||
إذا كان لديك أي أسئلة أو كنت تعتقد أن هذا القرار خاطئ، فلا تتردد في التواصل معنا لمزيد من التوضيح.
|
||
<br><br>
|
||
نقدّر تفهمك ونشكرك على اهتمامك.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td align="center" padding-top: 20px;">
|
||
<img src="https://imageats.s3.ap-south-1.amazonaws.com/signature.png" alt="We The UAE 2031" width="100%%">
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td style="padding-top: 20px; font-size: 12px; color: #777; border-top: 1px solid #ccc;">
|
||
<strong>تنويه:</strong> المعلومات الواردة في هذه الرسالة الإلكترونية والمرفقات الخاصة بها سرية وقد تكون محمية بموجب القانون. إذا لم تكن الشخص المعني باستلام هذه الرسالة، فيرجى إخطار المرسل وحذف هذه الرسالة فوراً.
|
||
<br><br>
|
||
<strong>Disclaimer:</strong> The information contained and transmitted within this email message is PRIVILEGED AND/OR CONFIDENTIAL. If you are not the intended recipient, please notify the sender and delete this email immediately.
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</body>
|
||
</html>
|
||
`, userName, userName)
|
||
} else {
|
||
// If the status is neither "Approved" nor "Denied", skip sending an email
|
||
return nil
|
||
}
|
||
|
||
// Create and send the email
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: userName,
|
||
Address: userEmail,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
err := app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
return fmt.Errorf("failed to send email: %v", err)
|
||
}
|
||
|
||
log.Printf("Status change email sent successfully to: %s", userEmail)
|
||
return nil
|
||
}
|