diff --git a/pb.zip b/pb.zip deleted file mode 100644 index 5211787..0000000 Binary files a/pb.zip and /dev/null differ 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

- - -

1.2 Usage Data

- - -

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

- - -

3. How We Share Your Information

-

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

- - -

4. Data Storage and Security

- - -

5. User Rights and Choices

- -

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 adb4c5f..c1fdcfd 100755 Binary files a/pb_data/data.db and b/pb_data/data.db differ diff --git a/pb_data/logs.db b/pb_data/logs.db index daa8a7c..872e8fc 100644 Binary files a/pb_data/logs.db and b/pb_data/logs.db differ 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" {