From fe106c9365bc83848d830b5b4481c5991a47fece Mon Sep 17 00:00:00 2001 From: Gowtham M Date: Sat, 27 Sep 2025 09:56:35 +0530 Subject: [PATCH] GWM merge code --- pb.zip | Bin 25044 -> 0 bytes pb/.env | 3 - pb/main.go | 2992 ------------------------------- pb/pb_public/privacy/index.html | 97 - pb_data/data.db | Bin 733184 -> 733184 bytes pb_data/logs.db | Bin 147456 -> 159744 bytes utils/calculation.go | 19 +- 7 files changed, 15 insertions(+), 3096 deletions(-) delete mode 100644 pb.zip delete mode 100644 pb/.env delete mode 100755 pb/main.go delete mode 100644 pb/pb_public/privacy/index.html diff --git a/pb.zip b/pb.zip deleted file mode 100644 index 521178732b8b9759d397166d3f221ea81dc421ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25044 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 Z;-8x=RYgRkXF;T=g9#1>CKd2}_dl4Pq}Tue diff --git a/pb/.env b/pb/.env deleted file mode 100644 index 2c4392e..0000000 --- a/pb/.env +++ /dev/null @@ -1,3 +0,0 @@ -APP_SIGNATURE = fcsc.gov.ae.X7pL9qZm2A - -BASE_URL = https://pocket.fcsc.gov.ae diff --git a/pb/main.go b/pb/main.go deleted file mode 100755 index d75f467..0000000 --- a/pb/main.go +++ /dev/null @@ -1,2992 +0,0 @@ -package main - -import ( - "context" - "crypto/rand" - "encoding/hex" - "encoding/json" - "encoding/xml" - "fmt" - "io/ioutil" - "log" - "math/big" - "net/http" - "net/mail" - "os" - "path/filepath" - "pocketbase/utils" - "strings" - "time" - - firebase "firebase.google.com/go" - "firebase.google.com/go/messaging" - "github.com/joho/godotenv" - "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" -) - -var baseUrl = os.Getenv("BASE_URL") - -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() { - - // load .env file - err := godotenv.Load() - if err != nil { - log.Println("No .env file found") - } - - var baseUrl = os.Getenv("BASE_URL") - - app = pocketbase.New() - - app.OnBeforeServe().Add(func(e *core.ServeEvent) error { - // Serve privacy policy static page - e.Router.GET("/privacy/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public/privacy"), false)) - - // API endpoint: returns JSON policy - e.Router.GET("/api/privacy", func(c echo.Context) error { - policy := map[string]string{ - "title": "Privacy Policy", - "content": "We respect your privacy. Data is collected only for authentication and secure service delivery.", - } - return c.JSON(http.StatusOK, policy) - }) - - return nil - }) - - // 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("/api/getImagePath", func(c echo.Context) error { - - fileName := c.QueryParam("file") - if fileName == "" { - return c.JSON(http.StatusBadRequest, map[string]string{ - "error": "missing file parameter", - }) - } - - // Static files folder - staticPath := "./pb_public" - fullPath := filepath.Join(staticPath, fileName) - - // Check if file exists - if _, err := os.Stat(fullPath); os.IsNotExist(err) { - return c.JSON(http.StatusNotFound, map[string]string{ - "error": "file not found", - }) - } - - // Serve the file directly (browser will show the image) - return c.File(fullPath) - }) - - // e.Router.Static("/static", "./pb_public") - - 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - 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"}) - } - - //check the user type - isOAuthLogin := user.GetString("is_oauth_login") - if isOAuthLogin != "0" { - return c.JSON(http.StatusOK, map[string]string{"status": "failed", "message": "Password reset is not available for accounts signed in with Apple or Google."}) - } - - 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://fcscapp.onelink.me/forgot-password/%s", email) - - body := fmt.Sprintf(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - -
- - إعادة تعيين كلمة المرور - تطبيق إحصاءات الإمارات العربية المتحدة
-

Reset your FCSC password -


-
- - - - - -
- Hello,

- Click on the link below to reset your password. -

- Reset Password -

- If you didn't ask to reset your password, you can ignore this email. -
- مرحبًا،

- يُرجى الضغط على الرابط أدناه لإعادة تعيين كلمة المرور الخاصة بك:

- إعادة تعيين كلمة المرور -

- إذا لم تطلب إعادة تعيين كلمة المرور، يمكنك تجاهل هذا البريد الإلكتروني. -
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- - `, resetURL, 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{"status": "success", "message": "Reset link sent to your email"}) - }) - - e.Router.GET("/api/auth/reset-password", func(c echo.Context) error { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - // 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - type RequestData struct { - Dataset string `json:"dataset"` - KPI string `json:"kpi"` - Language string `json:"language"` - ColorMode string `json:"color_mode"` - 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 - colorMode := requestData.ColorMode - - // 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"), - "chart_label_width": record.Get("chart_label_width"), - } - - 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"` - }{} - - header_color := "header_color_" + colorMode - body_color := "body_color_" + colorMode - border_color := "border_color_" + colorMode - 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 + ` AS header_color,` + body_color + ` AS body_color, ` + border_color + ` AS 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - language := c.QueryParam("language") - colorMode := c.QueryParam("color_mode") - - // Determine the column name dynamically - colorPatternColumn := "color_pattern_" + colorMode - - // 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 - query := fmt.Sprintf(` - SELECT - main_topic_en, - main_topic_ar, - main_topic_list_order, - %s AS color_pattern - FROM home_screen - GROUP BY main_topic_en, main_topic_ar, main_topic_list_order, %s - ORDER BY main_topic_list_order ASC - `, colorPatternColumn, colorPatternColumn) - - err := app.DB().NewQuery(query).All(&mainTopics) - - // 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/getHomePageData", func(c echo.Context) error { - - // language := c.QueryParam("language") - // colorMode := c.QueryParam("color_mode") - - // var colorPatternColumn string - // if colorMode == "dark" { - // colorPatternColumn = "color_pattern_dark" - // } else { - // colorPatternColumn = "color_pattern_light" - // } - - // var languageSpecificColumn string - // if language == "en" { - // languageSpecificColumn = "data_set_tile_heading_en" - // } else { - // languageSpecificColumn = "data_set_tile_heading_ar" - // } - - // // 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 - // query := fmt.Sprintf(` - // SELECT - // main_topic_en, - // main_topic_ar, - // main_topic_list_order, - // %s AS color_pattern - // FROM home_screen - // GROUP BY main_topic_en, main_topic_ar, main_topic_list_order, %s - // ORDER BY main_topic_list_order ASC - // `, colorPatternColumn, colorPatternColumn) - - // err := app.DB().NewQuery(query).All(&mainTopics) - - // // 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"` - // }{} - - // 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - language := c.QueryParam("language") - colorMode := c.QueryParam("color_mode") - - // Determine the column name dynamically - colorPatternColumn := "color_pattern_" + colorMode - - // 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 - - // Execute the query - query := fmt.Sprintf(` - SELECT - main_topic_en, - main_topic_ar, - main_topic_list_order, - %s AS color_pattern - FROM uae_numbers_screen - GROUP BY main_topic_en, main_topic_ar, %s - ORDER BY main_topic_list_order ASC - `, colorPatternColumn, colorPatternColumn) - - err := app.DB().NewQuery(query).All(&mainTopics) - - // 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 { - - apiKey := c.Request().Header.Get("APP_SIGNATURE") - if apiKey != os.Getenv("APP_SIGNATURE") { - return c.JSON(http.StatusForbidden, map[string]string{"error": "Invalid API Key"}) - } - - language := c.QueryParam("language") - user_id := c.QueryParam("user_id") - colorMode := c.QueryParam("color_mode") - - // 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 - color_pattern := "color_pattern_" + colorMode - 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 + ` AS 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("%s/verify-email?userId=%s", baseUrl, userID) - - // Email body - body := fmt.Sprintf(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - -
- - تطبيق إحصاءات الإمارات العربية المتحدة - يرجى التحقق من عنوان بريدك الإلكتروني
-

Please Verify Your Email Address – UAE Stats App -


-
- - - - - -
- Dear %s,

- Thank you for registering. Please click the link below to verify your email address: -

- Verify Email -

- If you did not register for this account, please ignore this email. -
- عزيزي %s،

- شكرًا لتسجيلك. يُرجى النقر على الرابط أدناه للتحقق من عنوان بريدك الإلكتروني: -

- تحقق من البريد الإلكتروني -

- إذا لم تكن قد قمت بتسجيل هذا الحساب، يُرجى تجاهل هذا البريد الإلكتروني. -
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- - - `, name, verificationURL, name, verificationURL) - - // 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, ` - - - - - -
-
-

Your Already Reviewed

-

You have already been reviewed. No further action is required.

-
- - - `) - } - - // 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, ` - - - - - -
- -

User Approved Successfully

-

The user has been approved, and the approval email has been sent.

-
- - - `) - }) - - 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, ` - - - - - -
-
-

Your Already Reviewed

-

You have already been reviewed. No further action is required.

-
- - - `) - } - - // 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", false) - 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, ` - - - - - - User Rejected - - - -
- -

User Rejected

-

Rejection email sent successfully.

-
- - - `) - }) - - 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("/verify-email", func(c echo.Context) error { - return verifyEmailHandler(c, baseUrl) - }) - 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") - userEmail := user.GetString("email") - - // 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(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - -
- - ملاحظات حول التطبيق الهاتفي للإحصاءات الإماراتي UAE
-

UAE Stats Mobile Application Feedback -


-
- - - - - -
- Hi Team,

- We have received new feedback from %s. Below are the details:

- Feedback Details: -
    -
  • User Name: %s
  • -
  • Email: %s
  • -
  • Date: %s
  • -


- Feedback: -
-										1. How was your experience with us today : %s
-										2. How good did we do in this aspect?
-										    * Ease of Use: %s
-										    * Quality: %s
-										    * Design: %s
-										    * Redundancy: %s
-										3. Tell us how we can improve  
-										    %s
-										
-
- مرحبا فريق العمل،

- لقد تلقينا تعليقات جديدة من %s. فيما يلي التفاصيل:

-
- تفاصيل الملاحظات: -
    -
  • اسم المستخدم: %s
  • -
  • البريد الإلكتروني: %s
  • -
  • التاريخ : %s
  • -


- تعليق : -
-										1. كيف كانت تجربتك معنا اليوم؟ : %s										
-										2. ما مدى نجاحنا في هذا الجانب؟
-										    * سهولة الاستخدام: %s
-										    * الجودة: %s
-										    * التصميم: %s
-										    * التكرار: %s
-										3. أخبرنا كيف يمكننا تحسين خدماتنا.؟
-										    %s
-										
-
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- -`, userName, userName, userEmail, formattedDate, emojiRating, easeOfUse, quality, design, redundancy, additionalFeedback, userName, userName, userEmail, formattedDate, emojiRating, easeOfUse, quality, design, redundancy, additionalFeedback) - - // 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( - `

Thanks for verifying your %s account!

-

Your code is: %d

-

Sincerely,
Support team.

`, - 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) - } -} - -// 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, baseUrl string) 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, ` - - - - - -
-
-

Email Already Verified

-

Your email has already been verified. No further action is required.

-
- - - `) - } - - // 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, ` - - - - - -
-
-

Your registration is pending for Admin Approval

-

Access will be granted once your account is approved.

-
- - - `) - - // 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, baseUrl) - if err != nil { - log.Printf("Failed to send admin email: %v", err) - } - }() - - return response - -} - -func sendAdminEmail(app *pocketbase.PocketBase, userID, username, userEmail string, baseUrl string) error { - - adminNameEn := "Admin" - adminNameAr := "المدير" - 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(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - -
- - تسجيل مستخدم جديد - تطبيق إحصاءات الإمارات العربية المتحدة - قيد المراجعة
-
-
New User Registration - UAE Stats App - Pending Review -


-
- - - - - -
- Dear %s,

- A new user has registered on UAE Stats
Mobile app and is awaiting your review. -

- Details
- Name: %s
- Email: %s -

- Please review the registration and take
appropriate action -

- -
- عزيزي %s،

- تم تسجيل مستخدم جديد في تطبيق إحصاءات الإمارات
- للهواتف المحمولة، وهو بانتظار مراجعتك. -

- التفاصيل
- الاسم: %s
- البريد الإلكتروني: %s -

- يرجى مراجعة التسجيل واتخاذ الإجراء المناسب
- الموافقة أو الرفض. -

- -
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- - - `, - adminNameEn, username, userEmail, userEmail, baseUrl, userID, baseUrl, baseUrl, userID, baseUrl, - adminNameAr, username, userEmail, userEmail, baseUrl, userID, baseUrl, baseUrl, userID, baseUrl) - - // Send email - message := &mailer.Message{ - From: mail.Address{ - Name: "FCSC", - Address: app.Settings().Meta.SenderAddress, - }, - To: []mail.Address{ - {Name: adminNameEn, 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(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - - - -
- - تمت الموافقة على التسجيل - إحصاءات الإمارات العربية المتحدة
-

Registration Approved – UAE Statistics App -


-
- - - - - -
- Dear %s,

- 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. -

- Please access your account using the
following link: Login. -

- If you have any questions or believe this
decision was made in error, please feel free
to contact us for further clarification. -
- عزيزي %s،

- يسرنا إبلاغك بأنه قد تمت الموافقة على تسجيلك
في تطبيق إحصاءات الإمارات. يمكنك الآن تسجيل
الدخول باستخدام بيانات الاعتماد التي أنشأتها. -

-
- يرجى الوصول إلى حسابك عبر الرابط التالي
تسجيل الدخول. -

- إذا كانت لديك أي استفسارات أو كنت تعتقد أن هذا
القرار خاطئ، فلا تتردد في التواصل معنا لمزيد
من التوضيح. -
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- - - `, userName, userName) - } else if newStatus == "Denied" { - subject = "Registration Update – FCSC" - body = fmt.Sprintf(` - - - - - - Registration Update – UAE Stats App - - - - - - -
- - - - - - - - - - - - - - - - -
- - تحديث التسجيل – تطبيق إحصاءات الإمارات العربية المتحدة
-

Registration Update – UAE Stats App -


-
- - - - - -
- Dear %s,

- 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. -

- If you have any questions or believe this
decision was made in error, please feel free
to contact us for further clarification. -

- We appreciate your understanding and thank
you for your interest. -
- عزيزي %s،

- نشكرك على تسجيلك في تطبيق إحصاءات
الإمارات للهواتف المحمولة. بعد مراجعة دقيقة،
يؤسفنا إبلاغك بأنه لم تتم الموافقة على تسجيلك
حتى الآن. -

- إذا كانت لديك أي أسئلة أو كنت تعتقد أن هذا القرار
خاطئ، فلا تتردد في التواصل معنا لمزيد من
التوضيح. -

- نقدّر تفهمك ونشكرك على اهتمامك. -
-
- We The UAE 2031 -
- تنبيه:
هذه الرسالة الإلكترونية و أيٌ من مرفقاتها قد تحتوي على معلومات سرية وهامة موجهه للشخص/الأشخاص المعنيين وعليه يرجى من المتلقي في حال تلقي الرسالة الإلكترونية عن طريق
الخطأ ولم يكن المعني بها، إخطار المرسل وحذفها من بريده الإلكتروني و كذلك إتلاف أي نسخ مطبوعة عنها حيث أنه يحظر عليه قراءة ونسخ ونشر أو توزيع أو استخدام هذه الرسالة
إلكترونية و أيٌ من مرفقاتها بأي شكل من الأشكال -
- Disclaimer:
The information contained in and transmitted with this e-mail message is PRIVILEGED AND/OR CONFIDENTIAL.If you are not the intended recipient, or have received the message by error, please notify the sender via E-Mail or over the telephone and delete this e-mail. You are not authorized to read, copy, disseminate, distribute or use this E-Mail or any of its attachments in any way. -
-
- - - `, 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 -} diff --git a/pb/pb_public/privacy/index.html b/pb/pb_public/privacy/index.html deleted file mode 100644 index 5a6491c..0000000 --- a/pb/pb_public/privacy/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - Privacy Policy - FCSC - - - -
-

- Privacy Policy -

- -

- Welcome to the Federal Competitiveness and Statistics Centre (FCSC) Mobile Application. - We value your privacy and data protection and are committed to safeguarding your personal - information. This Privacy Policy explains how we collect, use, share, and protect your information - when using this App. -

- -

- By using the App, you consent to the practices described in this Privacy Policy. -

- -

1. Information We Collect

-

1.1 Personal Information

-
    -
  • Name, email address, phone number (when registering an account).
  • -
  • User credentials (such as username and password).
  • -
  • Demographic data (if provided by the user).
  • -
- -

1.2 Usage Data

-
    -
  • Device information (IP address, operating system, device type).
  • -
  • App interactions (pages viewed, features used, actions performed).
  • -
  • Log data (error reports, login timestamps).
  • -
- -

1.3 Location Data

-

If enabled, we may collect location-based data (to enhance certain features).

- -

1.4 Cookies and Tracking Technologies

-

We may use cookies, analytics tools, and tracking technologies to improve App functionality and user experience.

- -

2. How We Use Your Information

-
    -
  • Provide and improve services.
  • -
  • Deliver insights, statistics, and reports.
  • -
  • Personalize user experience.
  • -
  • Ensure security and prevent unauthorized access.
  • -
  • Comply with UAE laws and regulations.
  • -
  • Communicate with users (inquiries, updates, support).
  • -
- -

3. How We Share Your Information

-

FCSC does not sell or trade your personal information. However, we may share data in the following cases:

-
    -
  • Government and Legal Compliance (UAE authorities, legal obligations).
  • -
  • Third-Party Service Providers (hosting, analytics, technical support).
  • -
  • Security and Fraud Prevention.
  • -
- -

4. Data Storage and Security

-
    -
  • AES-256 encryption to protect user data.
  • -
  • Stored on government-approved secure servers.
  • -
  • Access only by authorized personnel.
  • -
  • Data retained only as long as legally necessary.
  • -
- -

5. User Rights and Choices

-
    -
  • Access & Review your stored personal data.
  • -
  • Correction & Deletion of personal data.
  • -
  • Opt-Out of marketing communications.
  • -
  • Disable location services via device settings.
  • -
-

To exercise these rights, contact us at [Insert Official FCSC Email].

- -

6. Third-Party Links and Services

-

The App may contain links to external websites or services (e.g., UAE government portals). - FCSC is not responsible for third-party privacy practices. Users should review their respective Privacy Policies.

- -

7. Children's Privacy

-

The App is not intended for children under 13 years. We do not knowingly collect data from minors. - Parents or guardians may request removal of a child’s data by contacting FCSC.

- -

8. Changes to This Privacy Policy

-

We may update this Privacy Policy periodically to reflect changes in laws, technology, or user feedback. - The latest version will always be available in the App.

- -

9. Contact Information

-

For privacy-related inquiries, you may contact: info@fcsc.gov.ae

-
- - diff --git a/pb_data/data.db b/pb_data/data.db index adb4c5fd0046ef98c9879209838c19321c7ab60f..c1fdcfd25e2bc896df301cc742c93a931bee823b 100755 GIT binary patch delta 352 zcmZp8pwj?EEsQNpEzDb3SPgg?{xY)Ea~bmLu|eK zSRw^2b&ZS_3@oioKuFKT+-SPuZx)N`=N_=JPk(32BDCGdlqH;L`ouI2S>fXRqLTQ; z(2x7_wM1x9>7%0b*7lW&>jO?Yqo5d^EU98F)@`E@m!e zJGwbgA(fe_&1d?AWDa?zCh_STlR3h<4b4H;8yZ-dnogJ3=Cq#P@Qaae`iHw*vfC?D zI82#@&}?-CT6$57S$_MmG!D;9#w$Q??b?3o0*5R!)aT{URp#XlRd)8D{^1UX^mK_U9Lm$Ru5*YB0DTX%-o(n#)XKn8&&*_c;B}6O c>62vHEv6q>#>U(J;W`Hpb8i1|ol77B0EN?X+5i9m delta 301 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 F|NprrEadF}$zJBA@+UsveU%vKs z^p)Ei(O2HMdGoxTv|*^dv{urq*IrzE-MH}s|MHAV+Bv0N&~zz^%qc-N4N zvLPF!s=+KnDI0a!SS!gj^R8b?fJN%I{kOh;`xWOhm)?DEsMNe6OQvjLI8gs`-871( zQk9pVJM;F1Gt~=c3eht!t)98HdZzf!b1N%eyc&98wsJ_{pO4-pS9wGCUg8M+R_;ZP z9yxvK>F~W)oCDEY|f zl6&cf%eKZiv^xCD1i%>X6@M>$E`Y@_Q>baJY3GHghS1=b)LwSc> z^67BP)q<*AuS+G_x@1zE=W?yeU1jCyMA0hN zNikTcCBD1z{t1{IPoEBd(6A>*I}5bXJ1LSch9^YpwsTL1y;*7bIpevV9aU?|#&TJ1 zFB?*=pjC6KmRnA(z{`6J+RcH9#5=tWO>R9j{3a5(rR|A?ej3@GNhDY)WC~eXSvY(; zb2=QdI=H^2!u~i51HIefS*vqc>%T+yLg6E)pLr&H@4DS_Ri$J|rlQsA+5WF{-SN;{ z+4|o=R~8jjj=s6}`ua<2uP<|gurhjGi%Pn#DssW;ncj-GBFr5=edC$2t?gHqWnFEW zhX;zIkUBrDjSljY+G7_SV;>VzWCu6|Hc3O&Tr1ozYM8UsZ^5TQew8D z>!8QXOLaMG>!_WyfAiM4x%pd=Hp!$pmSqxIsZhugt!EZgfO$=ARBKM+|NgzF!t*R7 z<{2@aW*8=`)D+YC`oFwEjQk6ccSDi?5&8F#KY?HHg$N)5hyWsh2p|H803v`0AOeU0 zB7g`W0=tR8nfcpcU;V(sGxIM)s)>4m`Df>EL7K_>fVn5;*<-_cfJ1Oaha!JNuIs7D z|BL*e$p4P~FZlC0&xPJ!`pU6$NAF$#@JJ@ps&DIz$T5X7Co~e$^=li^xTGs_LS?AO zvwF@_M$`#sqMVI)3dPo@UZ{vtvCWIsI15S$mg9I<%$8qEt-n)PdvUuI+tf>!%yOfe z%fR<-c%L=3id@UQyF^M^T3sqxq`C7;a$C-C0I6(gH5p@5>10|wzho;&AQ3cW={zv$ zEJ7UoT{ca9H6C}kU#n?GWa8Plm8eU%TU~O#2C+@?TUt#p{P`ubqeDGVo<&uJZ-G|` zfA9Xqext!KT++T`QcaJi2bJB@wiQ*C;zEpxF2BXH7o#_oTB99pr<2(vzY<*oUWj~0 z&b_RdaUqe4C6dwQmtWerb@O~wRVs4yx}2|QE7A3`p;hHL3n>VTkcoa>DoTdZ%|hzD zzx2G*!-Z18tkt=#mZp|l<+M)^R~R;FR9IcxZWcRj&F*2)VkJ}AEBy8=8@FHOZ{~ya za5~2GY=TQakRArcW{@5Zc(MDThb>+t45|Kje~)80;Q}LGU{g^hy($Q+0uzINduN>d z{;7*(kevJAcQcuWTqu-{Qn}TXxni1g1_@{&RXJ~wpoS&`L(XePp?*mR8eWDytINi# z_HQtEn6i;MTU2DVP|qj@=f;-`Riy?4w14AsF@_g8f!mv5L4SF6pF7uR|Jl9J-XGno z-K(3PW=Qa2EWwW$5?%TI+$tFo)eogiCNCsQvMkh%d_E<}%~^&7&z!AmhMBq0CC8qv zR}_6iGbI(qO>eZwZ5_```fpIIAFM&cGxk6kfTAnvGE|NiZ>;o8Ntx`=gQsq{zAxe3!F=~)`=NFjk2gZ*D=ee3x zl_w1uF_sp)SVYsTWVX_%;mS6%`K(SiH}+FX{L9j@Xe*` zS6_|ZZpuc~EX&cGQr(OS(YIyEs7IIAu3cSO+S%!@4>U=;9wszN3vy9vsAg6Lmz27h z?Ld`aT5Z%!n#@&6sev~DP11TJN12i^>0vz46k5@hEZK}@$rM1Dfh2=H-!q|5_*dtS ze!Tu;C6g&sH&YF{ohX(yv&y$;>2GJvrh^4Lb!eOi?Ns~zfv)}P8hk8Wf{*A6q^C3R zaqRiyi%e+o%V*Q^OHY|{4ny@k-u1cGGa#l-N|1>K3Y7w`14>tP&~d60Ym^1 zKm-s0L;w*$1P}p401Ivmm>w$xO1LsrB|YlM`8) z0U6WL@hmX z*@}@@w%BS)Q&vFsqgqlZrdYME%1OD%l$JsEor)-Eovj3;7p3i%Zh);hyi+OaMNT%= za;>IoZ1OC~%7(0Se7aJof}K|><<5X?LuhI3Y9gVUiL{nf5)hv*e3{Reimi6J#8r~! zrmPYj#`%1)!nYHt?Ic?*%jL!r$i9>=ZgHC>zSL@#rHZ0`4P*;jEY~rN@|N5wDM_Ij zg`xQ?UTHO}e4)YT65FY~^fbu+lPLf&DWfVd+gp;(eidYYW!A*4&D3V6)!I%7YVs+N z{bjkLunjf8DYa#;AUB>QVNmc9LxyY9-NAm_HhRo_t&+ zAFJd;Bp;v`3VjC@L7{&RnxD|0gQ_R=&p@{m`Vk3n|Hq)c34I^bHK9KM{Y>aPppXgu zBhbKvJ_MCZ=pN`;LOY;T3AI3r5;8z-5~_lpBvd9O{5)tvLcas5kI>sB7XJn)H$t~T zn-TggP+x?uMd9NzQBYhY8VO4U!9EGULGXd2;j83hoqSv&A8X{}GWobfK3*Unnd9?6 zfEa+k3`Kr&q;ujU^M!vse`oIVxzED?1egJ@ojAYv*rK}lA5Q+{ zF)s46lYekRkNoJ68Gaq=I!Lc?M;AU`4l617M=t+J|F!0WBlbv8#{>BsY@qTCb z*m&vO!oBN#F=0v#yR8J|`FGt`o?lZc(WQD_ z=*Dnpol@yImBg!zmJlk#z!{iK$FXm%gl{T!C8t1~T>S!F~WJd?5me03v`0AOeU0B7g`W0*JtYMBwy%B0Lxs$U468`7!c~W8|x2 z5 z|9pg;aPfaSLQeSjquNh6`5(E{3-)ypUcNp;4u;~nw?@dpc06}^gdEJtbIT*-U}2s+ zH9}6f`hPb7x4kq*P(GhZDMgP?ZIpOyI^$7WsW5(^(G4k~>@+)KHYh&b>$H*^@ zk-soTo>@E@4IP?07CQdP(H|dPTZqB0_<9r(!1#YA3De^j;V~dDzz)r{w-Fv`g1uq< zKfkb$E-r^pc*DVfU99~l#{cughaF?w4x%BS5C;t7|J5`5Itm#HGKum3`m2v?n!)&g z82>L=OlO)EpU3!rBccjo{6CEUHyCH~VWLuY<7dJ)mpBoGc6P>6*9I~0QeXU|b1L<$ zZfI68%Dh&EQ1vDq@sIFWvV??~wa7(O#6mlE|7Z%{;B^>sQ;}PAEip8eDu!l*mRqT6 zB?u2n9Za+UZOzMBYwJjJZK_l&H6W-uO=^LdF zBD~96(O*}8GGU?myD$oeRG#re*e_6bIhJ6fa)xRY=r;M1e2){OQxWa!k(XhHPsM0 zXlC}ou4kNcn7s1)x#ygD#}hwyTdEF4grXLP)J9k}=l3)$qv4M%;azrYDHnxF9~3MW5c_ z4rbhZSu&_jue1_o&5llG!p=)Z!AhLntdnRQ{*;7%`Se*DbyKTm^OBlxz=@z%*bmx@ z@-aVZpVD19MbqAek(5|THO4ibXTYg^w+W>_Gt)D3B1J>$R-UlqgQRoNE-&ev1 zy$Fxuf97`BD{#m~IEIOU z`G+1%cBLFp_Rmy~+c*J6ja@4%c)Q$>Ex}vlE@ig1*hZ3|z!3AI+i()J9FH+&>SFAN zj_%1c_8)MFidxv0VO<<1LxPDVIB}S0(v|PCeek2&9CCaVdESJRM7$pUK8GoccJKGe zH0;~&lcC2m@JZJ9>i0`%x%B%a>iYBhq#AJM_eneG$?uc6@5b+waHJ2vU!DPX{BQal znK8zD-=`9<>;A;#UfXfzQN0)Ox)A|H01-e05CKF05kLeG0Ym^1c#sGn$$y2YK8AJp z)ZPTcdC{QXGh+|w&8boCk@e{a@pe5PeK>Vp?tFChxi!>u?gZ`}^mXhrl7Cn0tQ6pg z8GIoEhyWsh2p|H803v`0AOeWMfkWUb+5V#AKa%_fiw07UI$L+benQ885}bg3zuwbY zJc>GjN%a17j7y1$#NLOAnTFjz7}g7k`&~Z-B<>?|KQPyLFk;WtOxUg`j76v=QG$@T zKi*#miTlK9${H%JWqGC{*DD7e2xcnBZEy%qlOHyE1aFtyJQ2J_?owuJ%fm$6cdRD+ zIUXTl+qaQj+pFdYJD|3~}(PT#+d!e;LH(ov)xT7IX_L$~)HUJB`U;KTXo z>T@r2bTzqi(ATkF?f)0(CKpVF2p|H803v`0AOeU0B7g`W0{aR9wEvIOfDH5cG05LI ztWYPgv*X_3KZ-hm3HE;$&J#?F$)tz`fB=F2Fmm736)?BiwqK0gXEy3Vt&bSFZ|$0E zER9&f;xKEQ++>2_?eaXm1Vr0i z%4}`1Egu1!F?8fn&~kPsa^Ha<0PsKn5JsE}*_H3BO)EBL*AT#1{}}6EHlid9O}^_2*B|&^`YYoF^m9)5eUj-!1Mp$cA`8s6j=nwB7o5{K2F3iBVjhnz03F!(Cm&Z zg24szV4nYH5eJ0fhyq>tuI*s4pji+YXZLlV6HKuqc9?|}Y5N;)Dnk8W$e^jprw+tF z%-o}Dnne#9&P!dbvo=#)RX>~$bz=L>U5e!B|DWxxT4>}S&j#rk_mu)O%ant85dlO1 z5kLeG0Ym^1Km-s0L|_jPI1Y6#&b=9m{3IeQzIfu!vGS45p?^64gZVpipU-_Z_kH*Z zUx>hz2&^yMI~IO*ZDakVXk5~jcwSRgId3XjtsWl}em%aKE2>gcY}S;rvT1U{E|kGN zF&__KzqVmFqAr^z*z4=*+`_%+&z@Qc2FR zXLZ?lmHfs*o_gO-eAY4}pQ}k#S^W0e!Y7CN?Q|6~4nuF@Je2J=OApip*KNv124dRN zXon10GG(fyJy&&#QVViXYN%#bg|h2rwgX1uY+h^BOqwS3jK>u95cf#B>+HFfQxsa! zm8_*9C7ngnV8}2o8Qfk=*xk+XYNwr@hpfB5irxJvb@!LQ9bNc%eWuBr1=TCg*ir8p zMSC=3Pw<(A$vleP{cz3Lle_y#?CwXYyPs$*+^deB%e~tOL(W4Ki+Yd;b1!vvbz|Y) zxtVHpR*=3C@8a>pe-t_remAsGnM;R13%@(JboiZx=lp-B+z?{&VFV7yHgxnJ8XsRD zrN`H$T~NjdIAk8BA@kK;7&2TabS#G|u(5$z9x|QCmv>>v#6zJY@flUm=O24~e$i6i z7Nw?=*J?5Ncw*!_`0R=A&wbI+^Y%2%(G#D|g@&Z3X%_rWq1f8g3l&i+wt2B?>zz4{ zXT@y!wbc4Mg|!#AOJnuUU!kc&7x}GPGbbuqNmq?6y9$;U_(b-4$X&Xv1Wq!^ydzI5ZA;zsq(mD{(+Ud59%SFzbl7fUI= zDHoHeq*m`%kxp^x>~>@06=Ut~WQ!kL#Z$k%a4+L`72+nBDAu+s)nuow-i^R`_5T@oVEm}x!a!ww4a&GWV;NtB*17euhKhA>ZRuJ0G4a0fSCt#VxUS8VSu@UO=?dd|~4d$Wa z=-YYR(Q&7#%bky|KDXBzT}|#B^mW*gaVd<{Ls4*P@^~WN`ex~?6DPR$b6hN)6jP#b zu-yC6b00nT2R$+EGYANoBcN+*!%%}}wfH7=Q^NTF-BaEBA$tOL#_%c~v~MP_`T?8g zcn1c|-}e}tsQ}xHFklgV&%%HOVEljd+=uW5w_zVnAM;}9$tU38vv2F1#Y|K+WBS>_ z_T1m^@&A*n471AfF@YBc-1XgZvRA~{!-oLw|8f71f&Yg!IJp1U$cnd1`~Ord$&0xC z=dR=Ne_el!fKOyS#&5vvgMJ&q3z_~&(AFH__TMOEXWzG5O3K-b8IIx`a75ttpLu(v zst~vTjAizKab-^cBaIR>jXKYRO2D$*rU)=z>_RDE(3qhVJpOMbx~A)@d`Hf`te9~j zk%}df(dCz4+PHP|d{k8`a`d{KuV^dL^|GN=S@5rJH5x{1S0(y`e9y zF0q`Qgk5@%V>sagBVJ%rQ6{}Ah^q`AOYw}e{rAcrzKt(lk%hpx-LKo);JXRf*}?dK zc}dMTz?m8R+|kyLwtlaxqI-co__qG5IC~-jhyWsh2p|H803v`0AOeWM0YhN%cqVix z91b1*2Zt*Q@6LS=zdl;8_jU2uxuf@veIR8rtx`=gQsq{zAxe3!F`EO|1THiy#?nGc zNbWfmy6cW+QBgL2W(AWw!(AZ}&$>#*wUS&jL9cjA+g4OniVL88Tz-pXFGg=F zwMIMIPA9WT{@1F=6jpgL#<2ZkN4knkNB;go`slf%@%wTnqvr~`!6%a2tk~jnoYUhy zPtFOQ$i$LDnosS~ado{0?(1$y$3&q|C+l~EE&nu^f;;b%Lw|N7lc}--U)a*BiLGs; zsa2hRaJfBV&_i&XIjA1ru%ElW0?~m7itYk8U>Sn4Tw-d#(W>)$3EdA|7wV{jf z*gc`qpVhLngOM!d=ty4Jgh-ZM78UgO*WS+x_R4xGlhIOgZd)lajMg?3t~gt0*a?#| zkc<+EWZJ#5T6MuRX~;#{kPTR?%BHEW#^a7DY&Fe@OgtO65>+)*_E^f!FWD{^ORM0n zeSV3M4xki6-fBS9QZfM>D*PQZ(Ct2)!5JaSBv%uuRVEn|le}XQ^vXk_L;tongSVJ! zf~}|YPB~Rr0YQsyN&{1x1s+L3Iiawb*Wa!^uhtGleq<7hyWsh2p|H8 z03v`0AOeU0BCvlDScIm`AN!Y~#UGsb3H*mIL;w*$1P}p401-e05P|)Rz^A9KI@Z`+ znlEtGnkgmz8(UiX!WeS$dxM&Xw~mDZuZfiHJGlQC0GJ-MZ8f>|gQJEd9r z8`&27lWpjP9FeHBeaUN?pk*s$zC%ctk*;QD71+(4>7Z%Gt*zEOiYvhY-*7*u@<<42PkW;tu2=HED~B}R<# zDIuNS2WNXso-QXXF)(ok3|8h$rUCQY6YMORU6XFWVw{OMVE*3Z958#&)h1x}{v;l- zfbPT|FgKa(17@{mt^NWKu#}ODLcp?y7K(u7%yvwPv!Dd5-CAS<=Cv1|fSKKkQNSVw z7N~$_IEz-m{G%4KphXQXZo5)SZ@~*#m}e23x|9R69f7!xNzl(Ez1tB-PBy$&+}5ZQ zuMHnb$5Q(7k#vH;!yY`6UfyMW4Hkz8AOeU0B7g`W0*C-2fCwN0h`_^(0I~mv7o-sU z!xtie2p|Hxi@+Z~`2z4AzI*Y9M>3g8idRijQU#$@NY&Y0<2!60n&;T(mV6R+nGQ6mToyOKQXSKpU&5XZ%`aWgSmu|mZx!=P+2b|%T)9PR@JZt_ zX!+GV(sJdOX~;z7m!U^w@M*habn+|DrZD)F)>Rh!q;lJf{Hh%=4EZH?EI@t zob0i^Kr&TEhnX;aF4qJ(ZLQ9PIc>(?1UhZL-h?`BzWxL|ZO%U5U@Bz0M1!gFcqZg& zTRL)rp0<#o347WC`ecHsuHBi?r>(C?ADF5(hc_@)F82a3Z8HZJglRK73&gY;M=cmr zmod12%$B{kpiEneXMv&5eqikX$yG*(v1~ttYnT7;$jQULHC)F(K98IMCNIYJGxs3f z)Tb7FM4PaK8Fk0`^}wJx{+rV!@7+;;xjc8!aK~svk+x^2p-9>@%}^xmTVp5^_blH2SfD5WSa&G& zvs(Qv`KN#TbDVne^VrbehIa9w4R@I{jPGSHSWG zqsPl3Ag7XnOn|pF$l-s>Nfc7syn^059x!`vZV#BfKi>x|pl^f^a`EW&Z`6lIMBh#y z1X8!TK2UxVStj6MaE$jsD)!j$gB0O4sf0UUqAe@7%TF_1zETy+L25gdN zy_~uf+vtC=;{SF1{QKwsb?yI-+~)s39$I+Zv+=<{5dlQt(L>-5)ePb5zxP8qlSvm^ ziE=y1whgtCXvx10g@+GIsITqtNr+Vb$vzQCC3 zPuD?iEy7elBk7+ZCz`_q0~&w$9Pl9;qZ^nC;K8+H>N{^RyGt7|RUT>6! ztIw3Ct;wB(z77j(nohCq66;|`m1`xlOqGY+u-TksK(uvLGyJnm@hH!+F+oU({N6vy zt1Y!HZ07RY&E}R;=)?^<52)66>l~TjHb>W^ZV>hUjP|yIT$CEBnT6R-uAA8oco=ts z?oX{sx6Oxe8bOWB3IVfs&+ZDC8y@~8m>z>xUeT4TwcuHY6a;Jo6cV(4vL*+t)xAy! zEW%m41Ln6UUu!)NTEgHOAG8!}J|pe07vP}946Wxu`^E-l-19-e<~h#{0rU5tBW5bV zezpi$gtb~(&l>>?aGpT|<{x~%N`yW;;3*>Wi! diff --git a/utils/calculation.go b/utils/calculation.go index b0c2b74..b52680b 100755 --- a/utils/calculation.go +++ b/utils/calculation.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math" + "regexp" "sort" "strconv" "strings" @@ -467,14 +468,19 @@ func CalculateGroupedSum(observations []Observation, request ChartCalculationReq // Sort by value in descending order or given key sortByKey := request.SortBy + var nonDigit = regexp.MustCompile(`\D`) if sortByKey != "" { // Sort by given key sort.Slice(valueList, func(i, j int) bool { val1Str := valueList[i].ObsKey[sortByKey] val2Str := valueList[j].ObsKey[sortByKey] - val1, _ := strconv.ParseFloat(val1Str, 64) - val2, _ := strconv.ParseFloat(val2Str, 64) + // remove non-digits + val1Digits := nonDigit.ReplaceAllString(val1Str, "") + val2Digits := nonDigit.ReplaceAllString(val2Str, "") + + val1, _ := strconv.ParseInt(val1Digits, 10, 64) + val2, _ := strconv.ParseInt(val2Digits, 10, 64) sortActionKey := request.SortAction if sortActionKey == "DESC" { @@ -550,14 +556,19 @@ func CalculateGroupedSum(observations []Observation, request ChartCalculationReq // Sort by value in descending order or given key sortByKey := request.SortBy + var nonDigit = regexp.MustCompile(`\D`) if sortByKey != "" { // Sort by given key sort.Slice(result, func(i, j int) bool { val1Str := result[i].ObsKey[sortByKey] val2Str := result[j].ObsKey[sortByKey] - val1, _ := strconv.ParseFloat(val1Str, 64) - val2, _ := strconv.ParseFloat(val2Str, 64) + // remove non-digits + val1Digits := nonDigit.ReplaceAllString(val1Str, "") + val2Digits := nonDigit.ReplaceAllString(val2Str, "") + + val1, _ := strconv.ParseInt(val1Digits, 10, 64) + val2, _ := strconv.ParseInt(val2Digits, 10, 64) sortActionKey := request.SortAction if sortActionKey == "DESC" {