package main import ( "context" "crypto/rand" "encoding/hex" "encoding/json" "encoding/xml" "fmt" "io/ioutil" "log" "math/big" "net/http" "net/mail" "os" "pocketbase/utils" "strings" "time" firebase "firebase.google.com/go" "firebase.google.com/go/messaging" "github.com/labstack/echo/v5" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" // "github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/types" "google.golang.org/api/option" ) type EmailConfigurationResponse struct { Page int `json:"page"` PerPage int `json:"perPage"` TotalItems int `json:"totalItems"` TotalPages int `json:"totalPages"` Items []struct { Type string `json:"type"` Email string `json:"email"` } `json:"items"` } type GenericData struct { XMLName xml.Name `xml:"GenericData"` DataSet []Obs `xml:"DataSet>Obs"` } type Obs struct { ObsKey []ObsKey `xml:"ObsKey>Value"` // Dynamic key-value pairs ObsValue ObsValue `xml:"ObsValue"` } type ObsKey struct { ID string `xml:"id,attr"` // Attribute for 'id' Value string `xml:"value,attr"` // Attribute for 'value' } type ObsValue struct { Value string `xml:"value,attr"` // Attribute for 'value' } // Helper function to map ObsKey into a dictionary func mapObsKey(keys []ObsKey) map[string]string { result := map[string]string{} for _, key := range keys { result[key.ID] = key.Value } return result } // Helper function to clean XML namespace prefixes func removeNamespace(xmlBytes []byte) string { return strings.ReplaceAll(string(xmlBytes), "generic:", "") } // Function to fetch XML from an API and convert it to JSON func fetchAndConvertXMLToJSON(apiURL string, outputDir string, fileName string, tableName string, id string) ([]map[string]interface{}, error) { // Call the API and get the XML response resp, err := http.Get(apiURL) if err != nil { return nil, err } defer resp.Body.Close() // Read the response body body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } // Clean the XML by removing namespace prefixes cleanXML := []byte(removeNamespace(body)) // Parse the cleaned XML data var data GenericData err = xml.Unmarshal(cleanXML, &data) if err != nil { return nil, err } // Transform the data for JSON output response := []map[string]interface{}{} for _, obs := range data.DataSet { obsMap := map[string]interface{}{ "ObsKey": mapObsKey(obs.ObsKey), "ObsValue": obs.ObsValue, } response = append(response, obsMap) } // Convert the response to JSON jsonData, err := json.MarshalIndent(response, "", " ") if err != nil { return nil, err } // Save the JSON data to a file if required // Ensure the folder exists if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { return nil, err } // Save the file file := fmt.Sprintf("%s/%s.json", outputDir, fileName) if err := ioutil.WriteFile(file, jsonData, 0644); err != nil { return nil, err } // Find the charts record record, err := app.Dao().FindRecordById(tableName, id) if err != nil { // return c.JSON(500, map[string]interface{}{"code": 500,"message": "Error finding user.",}) } record.Set("file_status", true) if err := app.Dao().SaveRecord(record); err != nil { // return c.JSON(500, map[string]interface{}{"code": 500,"message": "Failed to verify user.",}) } return response, nil } // Function to read JSON data from a file func readJSONFromFile(folderName, fileName string) ([]map[string]interface{}, error) { // Construct the file path dynamically using the folder name filePath := fmt.Sprintf("%s/%s.json", folderName, fileName) // Read and return the JSON content from the file fileContent, err := ioutil.ReadFile(filePath) if err != nil { return nil, fmt.Errorf("failed to read file '%s': %w", filePath, err) } // Parse the JSON content from the file var jsonResponse []map[string]interface{} if err := json.Unmarshal(fileContent, &jsonResponse); err != nil { return nil, fmt.Errorf("failed to parse JSON from file '%s': %w", filePath, err) } return jsonResponse, nil } type ResponseItem struct { ObsKey map[string]string `json:"ObsKey"` ObsValue map[string]string `json:"ObsValue"` } type FilterJsonData struct { Order int `json:"order"` En string `json:"en"` Ar string `json:"ar"` } type FilterGroup struct { FilterKey string `json:"filter_key"` FilterData []interface{} `json:"filter_data"` FilterTextAndOrder FilterJsonData `json:"filter_text_and_order"` } func generateFilters(responseRaw []map[string]interface{}, filterKeys []string, filterJsonData map[string]FilterJsonData) ([]FilterGroup, error) { // Transform responseRaw to []ResponseItem var response []ResponseItem responseBytes, err := json.Marshal(responseRaw) // Marshal raw data to JSON if err != nil { log.Printf("Failed to marshal raw response: %v", err) return nil, err } if err := json.Unmarshal(responseBytes, &response); err != nil { // Unmarshal to []ResponseItem log.Printf("Failed to unmarshal response: %v", err) return nil, err } // Create a map to hold unique filter data filterMap := make(map[string]map[string]struct{}) for _, key := range filterKeys { filterMap[key] = make(map[string]struct{}) } // Populate filter data dynamically for _, item := range response { for _, key := range filterKeys { if value, exists := item.ObsKey[key]; exists { filterMap[key][value] = struct{}{} } } } // Convert filter map to the desired structure var filters []FilterGroup for key, values := range filterMap { filterData := make([]interface{}, 0, len(values)) for value := range values { filterData = append(filterData, value) } if key == "REF_AREA" { filterData = []interface{}{"AE-AZ", "AE-DU", "AE-SH", "AE-AJ", "AE-UQ", "AE-RK", "AE-FJ"} } filters = append(filters, FilterGroup{ FilterKey: key, FilterData: filterData, FilterTextAndOrder: filterJsonData[key], }) } return filters, nil } // Function to fetch filter data of dataset func fetchfilterJSON(kpi string) ([]FilterGroup, error) { // Define struct for the result var dataSetFilter struct { Id string `db:"id" json:"id"` FullDataUrl string `db:"full_data_url" json:"full_data_url"` FilterKeys string `db:"filter_keys" json:"filter_keys"` FileStatus bool `db:"file_status" json:"file_status"` DataSet string `db:"dataset" json:"dataset"` Kpi string `db:"kpi" json:"kpi"` ExclusionFiltersJSON string `db:"exclusion_filters_array" json:"exclusion_filters_array"` FilterJson string `db:"filter_json" json:"filter_json"` } // Query to get one row from the data_set_filter table err := app.DB(). Select("id", "full_data_url", "filter_keys", "file_status", "dataset", "kpi", "filter_json", "exclusion_filters_array"). From("data_set_filter"). Where(dbx.HashExp{"kpi": kpi}). One(&dataSetFilter) if err != nil { log.Printf("Failed to fetch data_set_filter data: %v", err) return nil, err } // Use a map to store the dynamic JSON keys var filterJson map[string]FilterJsonData // Parse the JSON err = json.Unmarshal([]byte(dataSetFilter.FilterJson), &filterJson) if err != nil { log.Fatalf("Error parsing JSON: %v", err) } // Fetch and convert XML to JSON if file is not saved if !dataSetFilter.FileStatus { // Check if FileStatus is false _, err := fetchAndConvertXMLToJSON(dataSetFilter.FullDataUrl, "filter_files", dataSetFilter.Kpi, "data_set_filter", dataSetFilter.Id) if err != nil { log.Printf("Failed to process URL '%s': %v", dataSetFilter.FullDataUrl, err) return nil, err } } // Read the JSON data from the file response, err := readJSONFromFile("filter_files", dataSetFilter.Kpi) if err != nil { log.Printf("Failed to read JSON for Kpi filter '%s': %v", dataSetFilter.Kpi, err) return nil, err } // Parse the filter keys filterKeys := strings.Split(dataSetFilter.FilterKeys, ",") // Generate filters filters, err := generateFilters(response, filterKeys, filterJson) if err != nil { log.Printf("Failed to generate filters: %v", err) return nil, err } // Parse exclusion filters JSON if it's not empty var exclusionFilters []utils.FilterGroup if dataSetFilter.ExclusionFiltersJSON != "" { err = json.Unmarshal([]byte(dataSetFilter.ExclusionFiltersJSON), &exclusionFilters) if err != nil { log.Printf("Failed to parse exclusion filters JSON: %v", err) return nil, err } // log.Printf("Parsed Exclusion Filters: %+v", exclusionFilters) } // Convert filters from []FilterGroup to []utils.FilterGroup manually var utilsFilters []utils.FilterGroup for _, f := range filters { utilsFilters = append(utilsFilters, convertToUtilsFilterGroup(f)) } // Apply the filter removal logic only if exclusionFilters has values if len(exclusionFilters) > 0 { filteredResult := utils.RemoveExcludedFilters(utilsFilters, exclusionFilters) // Convert back to []FilterGroup before returning var finalFilters []FilterGroup for _, f := range filteredResult { finalFilters = append(finalFilters, convertToFilterGroup(f)) } return finalFilters, nil } // If no exclusion filters, return the original filters return filters, nil } // Convert FilterGroup to utils.FilterGroup func convertToUtilsFilterGroup(f FilterGroup) utils.FilterGroup { return utils.FilterGroup{ FilterKey: f.FilterKey, FilterData: f.FilterData, FilterTextAndOrder: utils.FilterJsonData{ Order: f.FilterTextAndOrder.Order, En: f.FilterTextAndOrder.En, Ar: f.FilterTextAndOrder.Ar, }, } } // Convert utils.FilterGroup to FilterGroup func convertToFilterGroup(f utils.FilterGroup) FilterGroup { return FilterGroup{ FilterKey: f.FilterKey, FilterData: f.FilterData, FilterTextAndOrder: FilterJsonData{ Order: f.FilterTextAndOrder.Order, En: f.FilterTextAndOrder.En, Ar: f.FilterTextAndOrder.Ar, }, } } // LanguageSource represents the source language data structure type LanguageSource struct { Dataset string `json:"dataset"` Key string `json:"key"` Value string `json:"value"` ValueEn string `json:"value_en"` ValueAr string `json:"value_ar"` } func TranslateFilters(app *pocketbase.PocketBase, dataset string, filterGroups []FilterGroup, langKey string) ([]FilterGroup, error) { // Fetch source data from the PocketBase collection param := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset}) records, err := app.Dao().FindRecordsByExpr("charts_variables", param) if err != nil { return nil, fmt.Errorf("failed to fetch source data: %w", err) } // Parse the source data into a slice of LanguageSource var sourceData []LanguageSource for _, record := range records { source := LanguageSource{ Dataset: record.GetString("dataset"), Key: record.GetString("key"), Value: record.GetString("value"), ValueEn: record.GetString("value_en"), ValueAr: record.GetString("value_ar"), } sourceData = append(sourceData, source) } // Translate the filter data for i, filter := range filterGroups { // Create a map for quick lookup of translations translationMap := make(map[string]string) for _, source := range sourceData { var value string switch langKey { case "value_en": value = source.ValueEn case "value_ar": value = source.ValueAr default: continue } if source.Key == filter.FilterKey { translationMap[source.Value] = value } } if filter.FilterKey == "TIME_PERIOD" { // Skip translation for TIME_PERIOD continue } for j, v := range filter.FilterData { // Type assertion for interface{} to string value, ok := v.(string) if !ok { return nil, fmt.Errorf("unexpected type for filter data: %v", v) } // Translate the value if it exists in the map if translatedValue, exists := translationMap[value]; exists { filterGroups[i].FilterData[j] = translatedValue } } } return filterGroups, nil } // Generic function to filter JSON data dynamically based on key-value pairs func filterDynamicChartData(chartData []map[string]interface{}, keyToFilter, valueToExclude string) []map[string]interface{} { var filteredData []map[string]interface{} for _, item := range chartData { obsKey, ok := item["ObsKey"].(map[string]interface{}) if !ok { // Skip items without "ObsKey" or malformed data continue } // Check if the key exists and matches the value to exclude if obsKey[keyToFilter] == valueToExclude { continue } // Add item to filtered data filteredData = append(filteredData, item) } return filteredData } func generateToken() string { bytes := make([]byte, 32) _, err := rand.Read(bytes) if err != nil { log.Fatal(err) } return hex.EncodeToString(bytes) } var app *pocketbase.PocketBase func main() { const baseUrl = "https://pb.venbait.in" app = pocketbase.New() // Hook to send notifications when a new record is created app.OnRecordAfterCreateRequest("notification").Add(func(e *core.RecordCreateEvent) error { title_en := e.Record.GetString("title_en") message_en := e.Record.GetString("message_en") title_ar := e.Record.GetString("title_ar") message_ar := e.Record.GetString("message_ar") notificationID := e.Record.GetString("id") // Get the new notification ID // Initialize Firebase opt := option.WithCredentialsFile("firebase-adminsdk.json") firebaseApp, err := firebase.NewApp(context.Background(), nil, opt) if err != nil { log.Fatalf("Error initializing Firebase: %v", err) } fcmClient, err := firebaseApp.Messaging(context.Background()) if err != nil { log.Fatalf("Error getting FCM client: %v", err) } // Fetch users with valid device tokens users, err := app.Dao().FindRecordsByExpr("users", nil) // Adjust collection name if different if err != nil { return err } for _, user := range users { token := user.GetString("device_token") language := user.GetString("language") if token != "" { // tokens = append(tokens, token) var message *messaging.Message if language == "en" { message = &messaging.Message{ Token: token, Notification: &messaging.Notification{ Title: title_en, Body: message_en, }, } } else { message = &messaging.Message{ Token: token, Notification: &messaging.Notification{ Title: title_ar, Body: message_ar, }, } } response, err := fcmClient.Send(context.Background(), message) if err != nil { if err.Error() == "messaging/registration-token-not-registered" { log.Println("⚠️ Device token is invalid. Remove it from the database.") // Here you should remove the token from your database } else { log.Printf("Error sending message: %v", err) } } else { fmt.Printf("✅ Successfully sent message: %s\n", response) // Update the pushed_notification field var pushedNotifications []string // Get existing pushed_notification JSON field pushedNotificationsJSON := user.GetString("pushed_notification") if pushedNotificationsJSON != "" { err := json.Unmarshal([]byte(pushedNotificationsJSON), &pushedNotifications) if err != nil { log.Printf("⚠️ Error parsing pushed_notification for user %s: %v", user.GetString("id"), err) continue } } // Append the new notification ID if not already present alreadyExists := false for _, existingID := range pushedNotifications { if existingID == notificationID { alreadyExists = true break } } if !alreadyExists { pushedNotifications = append(pushedNotifications, notificationID) } // Convert back to JSON updatedPushedNotifications, err := json.Marshal(pushedNotifications) if err != nil { log.Printf("⚠️ Error marshalling pushed_notification JSON: %v", err) continue } // Update the user record in the database user.Set("pushed_notification", string(updatedPushedNotifications)) if err := app.Dao().SaveRecord(user); err != nil { log.Printf("❌ Error updating pushed_notification for user %s: %v", user.GetString("id"), err) } else { fmt.Printf("✅ Updated pushed_notification for user %s\n", user.GetString("id")) } } } } return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/pw/auth/confirm-password-reset/:token", func(c echo.Context) error { return c.File("pb_public/confirm_password_reset.html") }) e.Router.GET("/api/auth/request-password-reset", func(c echo.Context) error { email := c.QueryParam("email") if email == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "Email is required"}) } // Find user by email user, err := app.Dao().FindFirstRecordByData("users", "email", email) if err != nil { return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"}) } name := user.GetString("name") // Generate token token := generateToken() expiration := time.Now().Add(30 * time.Minute).UTC() // Store token in the database user.Set("pw_reset_token", token) user.Set("pw_reset_token_expiry", expiration) if err := app.Dao().SaveRecord(user); err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update user record"}) } // Send email subject := "Reset your FCSC password" resetURL := fmt.Sprintf("%s/api/auth/reset-password?token=%s", baseUrl, token) body := fmt.Sprintf(`

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.

Thanks,
Your App Team

`, resetURL) // Create the email message message := &mailer.Message{ From: mail.Address{ Name: "FCSC", Address: app.Settings().Meta.SenderAddress, }, To: []mail.Address{ { Name: name, Address: email, }, }, Subject: subject, HTML: body, } // Send the email err = app.NewMailClient().Send(message) if err != nil { log.Printf("Failed to send verification email to user: %v", err) return err } return c.JSON(http.StatusOK, map[string]string{"message": "Reset link sent to your email"}) }) e.Router.GET("/api/auth/reset-password", func(c echo.Context) error { token := c.QueryParam("token") if token == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "Token is required"}) } // Find user with the token user, err := app.Dao().FindFirstRecordByData("users", "pw_reset_token", token) if err != nil { return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"}) } // Convert PocketBase `types.DateTime` to `time.Time` expiryTime := user.GetDateTime("pw_reset_token_expiry").Time() log.Printf("expiryTime : '%v'", expiryTime) log.Printf("time.Now : '%v'", time.Now().UTC()) if expiryTime.Before(time.Now().UTC()) { return c.File("pb_public/token_expired.html") // return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Token expired"}) } // return c.JSON(http.StatusOK, map[string]string{"message": "Token is valid"}) // Serve the reset page return c.File("pb_public/confirm_password_reset.html") }) e.Router.GET("/api/login_success", func(c echo.Context) error { userID := c.QueryParam("id") if userID == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "User ID is required"}) } // Find user by ID record, err := app.Dao().FindRecordById("users", userID) if err != nil { log.Printf("User not found: %v", err) return c.JSON(http.StatusNotFound, map[string]string{"error": "User not found"}) } // Get current login count and convert it properly var loginCount int if count, ok := record.Get("login_count").(float64); ok { loginCount = int(count) // Convert float64 to int } else { loginCount = 0 // Default if not set } // Increment login count loginCount++ record.Set("login_count", loginCount) // Save the updated record if err := app.Dao().SaveRecord(record); err != nil { log.Printf("Failed to update login count: %v", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to update login count"}) } log.Printf("User %s login count updated to %d", userID, loginCount) return c.JSON(http.StatusOK, map[string]interface{}{ "message": "Success", "login_count": loginCount, }) }) e.Router.GET("/api/getNotification", func(c echo.Context) error { language := c.QueryParam("language") if language == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "language is required"}) } categoryColumn := "category_" + language titleColumn := "title_" + language messageColumn := "message_" + language id := c.QueryParam("id") if id != "" { // Define a single notification struct, not a slice var notification struct { Id string `db:"id" json:"id"` Category string `db:"category" json:"category"` Title string `db:"title" json:"title"` Message string `db:"message" json:"message"` Created string `db:"created" json:"created"` } query := `SELECT id, created, ` + categoryColumn + ` AS category, ` + titleColumn + ` AS title, ` + messageColumn + ` AS message FROM notification WHERE id = {:id}` err := app.DB().NewQuery(query).Bind(dbx.Params{"id": id}).One(¬ification) if err != nil { log.Printf("notification not found: %v", err) return c.JSON(http.StatusNotFound, map[string]string{"error": "notification not found"}) } return c.JSON(http.StatusOK, map[string]interface{}{ "message": "Success", "data": notification, }) } // Fetch all notifications if no ID is provided var notifications []struct { Id string `db:"id" json:"id"` Category string `db:"category" json:"category"` Title string `db:"title" json:"title"` Message string `db:"message" json:"message"` Created string `db:"created" json:"created"` } query := `SELECT id, created, ` + categoryColumn + ` AS category, ` + titleColumn + ` AS title, ` + messageColumn + ` AS message FROM notification` err := app.DB().NewQuery(query).All(¬ifications) if err != nil { log.Printf("notifications not found: %v", err) return c.JSON(http.StatusNotFound, map[string]string{"error": "notifications not found"}) } return c.JSON(http.StatusOK, map[string]interface{}{ "message": "Success", "data": notifications, }) }) e.Router.GET("/api/removeNotification", func(c echo.Context) error { userID := c.QueryParam("user_id") notificationID := c.QueryParam("notification_id") if userID == "" || notificationID == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "user_id and notification_id are required"}) } // Fetch the user by ID user, err := app.Dao().FindRecordById("users", userID) if err != nil { return c.JSON(http.StatusNotFound, map[string]string{"error": "user not found"}) } var pushedNotifications []string // Get existing pushed_notification JSON field pushedNotificationsJSON := user.GetString("pushed_notification") if pushedNotificationsJSON != "" { err := json.Unmarshal([]byte(pushedNotificationsJSON), &pushedNotifications) if err != nil { log.Printf("⚠️ Error parsing pushed_notification for user %s: %v", userID, err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to parse pushed_notification"}) } } // Remove the notification ID if it exists updatedNotifications := []string{} for _, existingID := range pushedNotifications { if existingID != notificationID { updatedNotifications = append(updatedNotifications, existingID) } } // Convert updated array back to JSON updatedJSON, err := json.Marshal(updatedNotifications) if err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update pushed_notification"}) } // Update the user record with the new pushed_notification field user.Set("pushed_notification", string(updatedJSON)) if err := app.Dao().SaveRecord(user); err != nil { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to save updated notifications"}) } return c.JSON(http.StatusOK, map[string]interface{}{ "message": "success", "pushed_notification": updatedNotifications, }) }) e.Router.GET("/api/pushNotification", func(c echo.Context) error { // type Request struct { // DeviceToken string `json:"device_token"` // } // var requestData Request // log.Printf("🔥 requestData: '%v'", requestData) // // Validate required fields // if requestData.DeviceToken == "" { // return c.JSON(http.StatusBadRequest, map[string]string{"error": "DeviceToken is required"}) // } // DeviceToken := requestData.DeviceToken DeviceToken := c.QueryParam("device_token") // Load Firebase credentials JSON file opt := option.WithCredentialsFile("firebase-adminsdk.json") app, err := firebase.NewApp(context.Background(), nil, opt) if err != nil { log.Printf("🔥 Error initializing Firebase App: '%v'", err) } client, err := app.Messaging(context.Background()) if err != nil { log.Printf("Error getting Messaging client: %v", err) } message := &messaging.Message{ Token: DeviceToken, Notification: &messaging.Notification{ Title: "Hello!", Body: "This is a test push notification By Gowtham.", }, } response, err := client.Send(context.Background(), message) if err != nil { if err.Error() == "messaging/registration-token-not-registered" { log.Println("⚠️ Device token is invalid. Remove it from the database.") // Here you should remove the token from your database } else { log.Printf("Error sending message: %v", err) } } else { fmt.Printf("✅ Successfully sent message: %s\n", response) } return c.JSON(http.StatusOK, map[string]string{"status": "success"}) }) e.Router.POST("/api/getDataSet", func(c echo.Context) error { type RequestData struct { Dataset string `json:"dataset"` KPI string `json:"kpi"` Language string `json:"language"` FilterData []utils.Filter `json:"filter_data"` } var requestData RequestData // Bind JSON request to struct if err := c.Bind(&requestData); err != nil { return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request format"}) } // Validate required fields if requestData.Dataset == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "dataset is required"}) } dataset := requestData.Dataset language := requestData.Language langKey := "value_" + language // Fetch all matching records from charts table param := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset}) records, err := app.Dao().FindRecordsByExpr("charts", param) if err != nil { log.Printf("Failed to fetch records for dataset '%s': %v", dataset, err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"}) } if len(records) == 0 { return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for the given dataset"}) } // Fetch all matching records from charts variables table languageSource, err := app.Dao().FindRecordsByExpr("charts_variables", param) if err != nil { log.Printf("Failed to fetch language source: %v", err) } // Convert languageSource to []map[string]interface{} languageSourceConverted := []map[string]interface{}{} for _, record := range languageSource { languageSourceConverted = append(languageSourceConverted, record.SchemaData()) } //construct chart and card data based of configuration/calculation/default var aggregatedResponse []map[string]interface{} for _, record := range records { // Extract the URL for each record apiURL := record.GetString("url") file_name := record.GetString("kpi_file_name") if apiURL == "" { log.Printf("No URL for record '%v'", record) continue // Skip records with no URL } // Fetch and convert XML to JSON saveToFile := record.GetBool("file_status") chartId := record.GetString("id") if !saveToFile { // Check if saveToFile is false _, err := fetchAndConvertXMLToJSON(apiURL, "kpi_files", file_name, "charts", chartId) if err != nil { log.Printf("Failed to process URL '%s': %v", apiURL, err) continue // Skip this record and proceed with others } } // Read the JSON data from the file chartData, err := readJSONFromFile("kpi_files", file_name) if err != nil { log.Printf("Failed to read JSON for KPI '%s': %v", file_name, err) } // unset key and value if value to be excluded keyToFilter := record.GetString("unset_key") valueToExclude := record.GetString("unset_value") if keyToFilter == "" || valueToExclude == "" { // fmt.Println("unset_key or unset_value is empty. Skipping processing.") } else { // Filter the data dynamically and update chartData chartData = filterDynamicChartData(chartData, keyToFilter, valueToExclude) } //logo url generating var logoURL string cardLogo := record.GetString("card_logo") if cardLogo == "" { logoURL = "" } else { logoURL = fmt.Sprintf("%s/api/files/charts/%s/%s", baseUrl, record.GetString("id"), cardLogo, ) } cResRaw := utils.TranslateChartData(chartData, languageSourceConverted, langKey) //responce data structure chartHeadingKey := "chart_heading_" + language chartSubHeadingKey := "chart_sub_heading_" + language tabHeadingKey := "tab_heading_" + language // Correct conditional assignment for "response" recordResult := map[string]interface{}{ "url": apiURL, "dataset": record.GetString("dataset"), "main_id": record.GetString("main_id"), "sub_id": record.GetString("sub_id"), "kpi": record.GetString("kpi"), "is_chart": record.GetString("is_chart"), "chart_type": record.GetString("chart_type"), "group_by": record.GetString("group_by"), "chart_heading": record.GetString(chartHeadingKey), "chart_sub_heading": record.GetString(chartSubHeadingKey), "tab_heading": record.GetString(tabHeadingKey), "unset_value": record.Get("unset_value"), "card_logo": logoURL, "order_id": record.Get("order_id"), "card_type_json": record.Get("card_type_json"), "tab_order": record.GetString("tab_order"), "chart_type_json": record.Get("chart_type_json"), "card_full_length": record.Get("card_full_length"), "chart_bar_color": record.Get("chart_bar_color"), "chart_height": record.Get("chart_height"), } var cRes []utils.Observation cResJSON, err := json.Marshal(cResRaw) if err != nil { fmt.Println("Error encoding chart data:", err) } err = json.Unmarshal(cResJSON, &cRes) if err != nil { fmt.Println("Error decoding chart data:", err) } if requestData.KPI == record.GetString("kpi") && len(requestData.FilterData) > 0 { filteredData, err := utils.FilterByMultipleKeys(cRes, requestData.FilterData) if err != nil { log.Fatal(err) } if 0 < len(filteredData) { cRes = filteredData } else { defaultYearsFilteredData, err := utils.DefaultYearFilter(cRes, record.GetString("default_latest_year_count")) if err != nil { fmt.Println("Error processing defaultYearsFilteredData:", err) } cRes = defaultYearsFilteredData } } else { defaultYearsFilteredData, err := utils.DefaultYearFilter(cRes, record.GetString("default_latest_year_count")) if err != nil { fmt.Println("Error processing defaultYearsFilteredData:", err) } // fmt.Println("defaultYearsFilteredData:", defaultYearsFilteredData) cRes = defaultYearsFilteredData } var cardResults []map[string]string if record.GetString("is_chart") == "false" { // card data calculations // Extract `card_type` JSON from `record.Get("card_type")` requestData := record.Get("card_type_json") requestJSON, err := json.Marshal(requestData) // Convert interface{} to JSON string if err != nil { fmt.Println("Error encoding request data:", err) } // Decode JSON into an array of `utils.Request` var requests []utils.CardCalculationRequest err = json.Unmarshal(requestJSON, &requests) if err != nil { fmt.Println("Error decoding request data:", err) } if record.GetString("custom_calculation") == "true" { customCalculationReq := record.Get("chart_custom_calculation_json") var chartCustomCalculationRequest utils.ChartCalculationRequest jsonData, err := json.Marshal(customCalculationReq) if err != nil { log.Println("Error marshaling to JSON:", err) } err = json.Unmarshal(jsonData, &chartCustomCalculationRequest) if err != nil { log.Println("Error unmarshalling JSON:", err) } customResult, err := utils.CalculateGroupedSum(cRes, chartCustomCalculationRequest) if err != nil { fmt.Println("Error:", err) } cRes = customResult } // Process each request and store results for _, req := range requests { result, err := utils.CardCalculation(cRes, req) if err != nil { fmt.Println("Error processing data:", err) continue // Skip this request and move to the next } cardResults = append(cardResults, result) } recordResult["response"] = cardResults } else { if record.GetString("custom_calculation") == "false" { recordResult["response"] = cRes } else { // fmt.Println("cRes-------", cRes) customCalculationReq := record.Get("chart_custom_calculation_json") var chartCustomCalculationRequest utils.ChartCalculationRequest jsonData, err := json.Marshal(customCalculationReq) if err != nil { log.Println("Error marshaling to JSON:", err) } err = json.Unmarshal(jsonData, &chartCustomCalculationRequest) if err != nil { log.Println("Error unmarshalling JSON:", err) } customResult, err := utils.CalculateGroupedSum(cRes, chartCustomCalculationRequest) if err != nil { fmt.Println("Error:", err) } recordResult["response"] = customResult } } // Add the result to the aggregated response aggregatedResponse = append(aggregatedResponse, recordResult) } //-------------------- // get all filter based on kpi var dataSetKpis []struct { Kpi string `db:"kpi" json:"kpi"` } err = app.DB().Select("kpi").From("charts").Where(dbx.HashExp{"dataset": dataset}).GroupBy("kpi").All(&dataSetKpis) if err != nil { log.Fatalf("Failed to get kpi groupby data: %v", err) } aggregatedFilterResponse := make(map[string]interface{}) for _, dataSetKpi := range dataSetKpis { kpi := dataSetKpi.Kpi filterData, err := fetchfilterJSON(kpi) // Translate filter data using the desired dataset and language key fRes, err := TranslateFilters(app, dataset, filterData, langKey) if err != nil { log.Fatalf("Error translating filters: %v", err) } aggregatedFilterResponse[kpi] = fRes } //-------------------- //mainTopic kpi heading and screen color headingAndColor := struct { DataSet string `db:"data_set" json:"data_set"` MainTopic string `db:"main_topic" json:"main_topic"` SubTopic string `db:"sub_topic" json:"sub_topic"` DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"` HeaderColor string `db:"header_color" json:"header_color"` BodyColor string `db:"body_color" json:"body_color"` BorderColor string `db:"border_color" json:"border_color"` }{} data_set_tile_heading := "data_set_tile_heading_" + language main_topic := "main_topic_" + language sub_topic := "sub_topic_" + language query := ` SELECT id,data_set,` + data_set_tile_heading + ` AS data_set_tile_heading,` + main_topic + ` AS main_topic,` + sub_topic + ` AS sub_topic,header_color,body_color,border_color FROM uae_numbers_screen WHERE data_set = {:data_set} ` err = app.DB().NewQuery(query).Bind(dbx.Params{"data_set": dataset}).One(&headingAndColor) if err != nil { log.Printf("Failed to fetch data-set for sub topic %s: %v", dataset, err) } //--------------------- // Create the response structure response := map[string]interface{}{ "new_filter_data": aggregatedFilterResponse, "data": aggregatedResponse, "screen_heading_and_color": headingAndColor, "kpi": requestData.KPI, "filter_data": requestData.FilterData, } if len(aggregatedResponse) == 0 { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "No data could be fetched from the provided URLs"}) } return c.JSON(http.StatusOK, response) }) e.Router.GET("/api/getHomePageData", func(c echo.Context) error { language := c.QueryParam("language") // Define the struct for holding the query results mainTopics := []struct { MainTopicEn string `db:"main_topic_en" json:"main_topic_en"` MainTopicAr string `db:"main_topic_ar" json:"main_topic_ar"` MainTopicListOrder string `db:"main_topic_list_order" json:"main_topic_list_order"` ColorPattern string `db:"color_pattern" json:"color_pattern"` }{} // Execute the query err := app.DB(). Select("main_topic_en", "main_topic_ar", "main_topic_list_order", "color_pattern"). From("home_screen"). GroupBy("main_topic_en", "main_topic_ar", "color_pattern"). OrderBy("main_topic_list_order ASC"). All(&mainTopics) if err != nil { log.Printf("Failed to fetch home_screen data: %v", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"}) } // If no records are found if len(mainTopics) == 0 { return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for home screen"}) } // Final result structure result := []map[string]interface{}{} // Loop through main topics and fetch sub-topic data for each for _, mainTopic := range mainTopics { dataSets := []struct { DataSet string `db:"data_set" json:"data_set"` DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"` ValueSource string `db:"value_source" json:"value_source"` Value string `db:"value" json:"value"` DataSetListOrder string `db:"data_set_list_order" json:"data_set_list_order"` }{} languageSpecificColumn := "data_set_tile_heading_" + language query := ` SELECT data_set,` + languageSpecificColumn + ` AS data_set_tile_heading,value_source,value, data_set_list_order FROM home_screen WHERE main_topic_en = {:topic}` err := app.DB(). NewQuery(query). Bind(dbx.Params{ "topic": mainTopic.MainTopicEn, }). All(&dataSets) if err != nil { log.Printf("Error fetching data sets: %v", err) continue } var MainTopic string if language == "en" { MainTopic = mainTopic.MainTopicEn } else { MainTopic = mainTopic.MainTopicAr } // Add the main topic and its sub-topics to the result result = append(result, map[string]interface{}{ "main_topic": MainTopic, "main_topic_list_order": mainTopic.MainTopicListOrder, "color_pattern": mainTopic.ColorPattern, "tile_data": dataSets, }) } // Send the combined result as JSON return c.JSON(http.StatusOK, result) }) e.Router.GET("/api/getUAENumbersData", func(c echo.Context) error { language := c.QueryParam("language") // Define the struct for holding the query results mainTopics := []struct { MainTopicEn string `db:"main_topic_en" json:"main_topic_en"` MainTopicAr string `db:"main_topic_ar" json:"main_topic_ar"` MainTopicListOrder string `db:"main_topic_list_order" json:"main_topic_list_order"` ColorPattern string `db:"color_pattern" json:"color_pattern"` }{} // Execute the query err := app.DB(). Select("main_topic_en", "main_topic_ar", "main_topic_list_order", "color_pattern"). From("uae_numbers_screen"). GroupBy("main_topic_en", "main_topic_ar", "color_pattern"). OrderBy("main_topic_list_order ASC"). All(&mainTopics) if err != nil { log.Printf("Failed to fetch uae_numbers_screen data: %v", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"}) } // If no records are found if len(mainTopics) == 0 { return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for home screen"}) } // Final result structure result := []map[string]interface{}{} // Loop through main topics and fetch sub-topic data for each for _, mainTopic := range mainTopics { subTopics := []struct { SubTopicEn string `db:"sub_topic_en" json:"sub_topic_en"` SubTopicAr string `db:"sub_topic_ar" json:"sub_topic_ar"` SubTopicListOrder string `db:"sub_topic_list_order" json:"sub_topic_list_order"` }{} // Query to get sub-topics for the current main topic err := app.DB(). Select("sub_topic_en", "sub_topic_ar", "sub_topic_list_order"). From("uae_numbers_screen"). Where(dbx.HashExp{"main_topic_en": mainTopic.MainTopicEn}). GroupBy("sub_topic_en", "sub_topic_ar"). OrderBy("sub_topic_list_order ASC"). All(&subTopics) if err != nil { log.Printf("Failed to fetch sub-topics for main topic %s: %v", mainTopic.MainTopicEn, err) continue } result2 := []map[string]interface{}{} // Loop through sub Topics topics and fetch dataSet data for each for _, subTopic := range subTopics { dataSets := []struct { DataSet string `db:"data_set" json:"data_set"` DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"` ValueSource string `db:"value_source" json:"value_source"` Value string `db:"value" json:"value"` DataSetListOrder string `db:"data_set_list_order" json:"data_set_list_order"` }{} languageSpecificColumn := "data_set_tile_heading_" + language query := ` SELECT data_set,` + languageSpecificColumn + ` AS data_set_tile_heading,value_source,value, data_set_list_order FROM uae_numbers_screen WHERE sub_topic_en = {:topic}` err := app.DB(). NewQuery(query). Bind(dbx.Params{ "topic": subTopic.SubTopicEn, }). All(&dataSets) // languageSpecificColumn := "data_set_tile_heading_" + language // // Query to get data-set for the current sub topic // err := app.DB(). // Select("data_set", languageSpecificColumn, "value_source", "value", "data_set_list_order"). // From("uae_numbers_screen"). // Where(dbx.HashExp{"sub_topic_en": subTopic.SubTopicEn}). // OrderBy("data_set_list_order ASC"). // All(&dataSets) if err != nil { log.Printf("Failed to fetch data-set for sub topic %s: %v", subTopic.SubTopicEn, err) continue } var SubTopic string if language == "en" { SubTopic = subTopic.SubTopicEn } else { SubTopic = subTopic.SubTopicAr } result2 = append(result2, map[string]interface{}{ "sub_topic": SubTopic, "sub_topic_list_order": subTopic.SubTopicListOrder, "tile_data": dataSets, }) } var MainTopic string if language == "en" { MainTopic = mainTopic.MainTopicEn } else { MainTopic = mainTopic.MainTopicAr } // Add the main topic and its sub-topics to the result result = append(result, map[string]interface{}{ "main_topic": MainTopic, "main_topic_list_order": mainTopic.MainTopicListOrder, "color_pattern": mainTopic.ColorPattern, "sub_topics": result2, }) } // Send the combined result as JSON return c.JSON(http.StatusOK, result) }) e.Router.GET("/api/getUserBookmark", func(c echo.Context) error { language := c.QueryParam("language") user_id := c.QueryParam("user_id") // Define the struct for holding the query results bookmarks := []struct { Id string `db:"id" json:"id"` UserId string `db:"user_id" json:"user_id"` Dataset string `db:"dataset" json:"dataset"` }{} // Execute the query err := app.DB(). Select("id", "user_id", "dataset"). From("bookmark"). Where(dbx.HashExp{"user_id": user_id}). All(&bookmarks) // return c.JSON(http.StatusOK, bookmarks) if err != nil { log.Printf("Failed to fetch bookmark data: %v", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to fetch records"}) } // If no records are found if len(bookmarks) == 0 { return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found for Bookmark"}) } // Final result structure result := []map[string]interface{}{} // Loop through main topics and fetch sub-topic data for each for _, bookmark := range bookmarks { dataSets := struct { DataSet string `db:"data_set" json:"data_set"` MainTopic string `db:"main_topic" json:"main_topic"` SubTopic string `db:"sub_topic" json:"sub_topic"` DataSetTileHeading string `db:"data_set_tile_heading" json:"data_set_tile_heading"` ValueSource string `db:"value_source" json:"value_source"` Value string `db:"value" json:"value"` ColorPattern string `db:"color_pattern" json:"color_pattern"` }{} data_set_tile_heading := "data_set_tile_heading_" + language main_topic := "main_topic_" + language sub_topic := "sub_topic_" + language query := ` SELECT id,data_set,` + data_set_tile_heading + ` AS data_set_tile_heading,` + main_topic + ` AS main_topic,` + sub_topic + ` AS sub_topic,color_pattern,value_source,value FROM uae_numbers_screen WHERE data_set = {:bookmark} ` err := app.DB(). NewQuery(query). Bind(dbx.Params{ "bookmark": bookmark.Dataset, }). One(&dataSets) log.Printf("query %s: %v", bookmark.Dataset, query) if err != nil { log.Printf("Failed to fetch data-set for sub topic %s: %v", bookmark.Dataset, err) continue } // Add the main topic and its sub-topics to the result result = append(result, map[string]interface{}{ "id": bookmark.Id, "data_set": dataSets.DataSet, "data_set_tile_heading": dataSets.DataSetTileHeading, "value_source": dataSets.ValueSource, "value": dataSets.Value, "main_topic": dataSets.MainTopic, "sub_topic": dataSets.SubTopic, "color_pattern": dataSets.ColorPattern, }) } // Send the combined result as JSON return c.JSON(http.StatusOK, result) }) return nil }) //send mail while status changed from app admin app.OnRecordBeforeUpdateRequest().Add(func(e *core.RecordUpdateEvent) error { if e.Collection.Name == "users" { originalRecord, err := app.Dao().FindRecordById("users", e.Record.GetString("id")) if err != nil { return fmt.Errorf("failed to fetch original record: %v", err) } oldStatus := originalRecord.GetString("status") newStatus := e.Record.GetString("status") // Check if the status has changed if oldStatus != newStatus { err := sendStatusChangeEmail(app, e.Record, newStatus) if err != nil { return err } } } return nil }) //Hook to send a verification link to register user app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error { if e.Record.Collection().Name == "users" { // Retrieve user details name := e.Record.GetString("uname") email := e.Record.GetString("email") userID := e.Record.Id // User ID imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo") if err != nil { log.Printf("Failed to fetch logo from images collection: %v", err) return nil } // Construct the logo URL logoURL := fmt.Sprintf("%s/api/files/images/%s/%s", baseUrl, imageRecord.Id, imageRecord.GetString("file"), ) // Email subject subject := "FCSC Verify Your Email to Complete Registration" // Generate a verification URL (example: using user ID or token) verificationURL := fmt.Sprintf("https://pb.venbait.in/verify-email?userId=%s", userID) // Email body body := fmt.Sprintf(`

Dear %s,

Thank you for registering with FCSC. To complete your registration and activate your account, please verify your email address by clicking the link below:

Verify Email

If you did not register for this account, please ignore this email.


Best regards,

FCSC Logo `, name, verificationURL, logoURL) // Create the email message message := &mailer.Message{ From: mail.Address{ Name: "FCSC", Address: app.Settings().Meta.SenderAddress, }, To: []mail.Address{ { Name: name, Address: email, }, }, Subject: subject, HTML: body, } // Send the email err = app.NewMailClient().Send(message) if err != nil { log.Printf("Failed to send verification email to user: %v", err) return err } log.Println("Verification email sent successfully to the user.") } return nil }) // Hook to send an email after a user is created app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/api/custom/approve", func(c echo.Context) error { userId := c.QueryParam("userId") if userId == "" { return c.JSON(400, map[string]interface{}{ "code": 400, "message": "Missing userId query parameter.", }) } // Find the user record record, err := app.Dao().FindRecordById("users", userId) if err != nil { return c.JSON(500, map[string]interface{}{ "code": 500, "message": "Error finding user.", }) } // Check if the email is already verified if record.GetBool("reviewed") { return c.HTML(http.StatusOK, `

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", true) record.Set("status", newStatus) record.Set("reviewed", true) if err := app.Dao().SaveRecord(record); err != nil { return c.JSON(500, map[string]interface{}{ "code": 500, "message": "Failed to verify user.", }) } // Send the status change email err = sendStatusChangeEmail(app, record, newStatus) if err != nil { return c.JSON(500, map[string]interface{}{ "code": 500, "message": "Failed to send rejection email.", }) } // Render a success message as an HTML response return c.HTML(http.StatusOK, ` 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("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false)) return nil }) //Feedback Mail Trigger app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error { if e.Record.Collection().Name == "feedback" { // Retrieve feedback details feedback := e.Record userId := feedback.GetString("userId") emojiRating := feedback.GetString("emoji_rating") easeOfUse := feedback.GetString("ease_of_use") quality := feedback.GetString("quality") design := feedback.GetString("design") redundancy := feedback.GetString("redundancy") additionalFeedback := feedback.GetString("feedback") if additionalFeedback == "" { additionalFeedback = "No additional feedback provided." } // Fetch user details using userId user, err := app.Dao().FindRecordById("users", userId) if err != nil { log.Printf("Error fetching user details: %v\n", err) return err } userName := user.GetString("uname") // Extract and format the creation time createdTime := e.Record.Get("created").(types.DateTime) // Extract created time feedbackDateTime := createdTime.Time().UTC() // Convert to time.Time in UTC // Format the date and time formattedDate := feedbackDateTime.Format("02-01-2006") // DD-MM-YYYY formattedDateTime := feedbackDateTime.Format("02.01.2006 15:04 UTC") // DD.MM.YYYY HH:mm UTC // Admin details adminName := "FCSC" // Email subject subject := "UAE Stats Feedback" imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo") if err != nil { log.Printf("Failed to fetch logo from images collection: %v", err) return nil } // Construct the logo URL logoURL := fmt.Sprintf("%s/api/files/images/%s/%s", baseUrl, imageRecord.Id, imageRecord.GetString("file"), ) // Email body in HTML format body := fmt.Sprintf(`

Dear %s,

You have received new feedback from a user through the mobile application.

User Details:

Feedback:

					1. How was your experience with us today? Rating: %s
					2. How did we perform in key areas?
						1. Ease of Use: %s
						2. Quality: %s
						3. Design: %s
						4. Redundancy: %s
					3. Additional Feedback:
						1. %s
					

Thank you,

FCSC Logo `, adminName, userName, formattedDate, formattedDateTime, emojiRating, easeOfUse, quality, design, redundancy, additionalFeedback, logoURL) // Define the target type (e.g., "smtp") targetType := "feedback_receive_mail" // Fetch the email configuration record where type matches targetType emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", targetType) if err != nil { log.Printf("Failed to fetch email configuration for type '%s': %v", targetType, err) return fmt.Errorf("email configuration not found for type '%s'", targetType) } // Get the email address feedbackEmail := emailConfig.GetString("email") if feedbackEmail == "" { log.Printf("No email address configured for type '%s'", targetType) return fmt.Errorf("no email address configured for type '%s'", targetType) } // Create the email message message := &mailer.Message{ From: mail.Address{ Name: "FCSC", Address: app.Settings().Meta.SenderAddress, }, To: []mail.Address{ { Name: adminName, Address: feedbackEmail, }, }, Subject: subject, HTML: body, } // Send the email err = app.NewMailClient().Send(message) if err != nil { log.Printf("Failed to send feedback email to admin: %v", err) return err } log.Println("Feedback email sent successfully to the admin.") } return nil }) app.OnRecordAfterCreateRequest("otp_requests").Add(func(e *core.RecordCreateEvent) error { // Ensure the record has an email field email, ok := e.Record.Get("email").(string) if !ok || email == "" { log.Println("Invalid or missing email field.") return nil // or handle this case appropriately } // Generate the OTP otp, err := generateOTP() if err != nil { log.Printf("Failed to generate OTP: %v\n", err) return err } // Calculate the expiration time (5 minutes from now) expiresAt := time.Now().Add(5 * time.Minute) // Create a mail.Address for the sender sender := mail.Address{ Name: "FCSC", Address: app.Settings().Meta.SenderAddress, // Ensure this is a valid email } // Create the email message message := &mailer.Message{ From: sender, To: []mail.Address{{Address: email}}, // Wrap the recipient email Subject: "YOUR VERIFICATION CODE", HTML: fmt.Sprintf( `

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) } } const baseUrl = "https://pb.venbait.in" // generateOTP generates a random 4-digit OTP func generateOTP() (int, error) { max := big.NewInt(10000) // 4-digit numbers range: 0000 to 9999 n, err := rand.Int(rand.Reader, max) if err != nil { return 0, err } return int(n.Int64()), nil } // Email verification handler func verifyEmailHandler(c echo.Context) error { log.Println("Starting verifyEmailHandler") // Get the userId from the query parameter userID := c.QueryParam("userId") if userID == "" { return c.JSON(http.StatusBadRequest, map[string]string{ "message": "User ID not provided", }) } // Check if app is properly initialized if app == nil { log.Fatal("PocketBase app is not initialized") return c.JSON(http.StatusInternalServerError, map[string]string{ "message": "Internal server error", }) } // Find the user record in PocketBase by userId record, err := app.Dao().FindRecordById("users", userID) if err != nil { log.Printf("Error finding user: %v", err) return c.JSON(500, map[string]interface{}{ "code": 500, "message": "Error finding user.", }) } // Check if the email is already verified if record.GetBool("user_mail_verify") { return c.HTML(http.StatusOK, `

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) if err != nil { log.Printf("Failed to send admin email: %v", err) } }() return response } func sendAdminEmail(app *pocketbase.PocketBase, userID, username, userEmail string) error { adminName := "Admin" subject := "New User Registration Pending Review" // Fetch the image file URL from the PocketBase collection 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"), ) body := fmt.Sprintf(`

Dear %s,

A new user has registered and is awaiting your review.

Details:

Name: %s
Email: %s

Please review the registration and take appropriate action:

Approve Reject

Thank you,

FCSC Logo `, adminName, username, userEmail, userID, userID, logoURL) emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", "admin_receive_mail") if err != nil { log.Printf("Failed to fetch email configuration: %v", err) return nil } adminEmail := emailConfig.GetString("email") // Send email message := &mailer.Message{ From: mail.Address{ Name: "FCSC", Address: app.Settings().Meta.SenderAddress, }, To: []mail.Address{ {Name: adminName, Address: adminEmail}, }, Subject: subject, HTML: body, } if err := app.NewMailClient().Send(message); err != nil { log.Printf("Failed to send admin email: %v", err) return nil } 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") 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(`

Dear %s,

We are pleased to inform you that your registration with FCSC has been approved. You can now log in using the credentials that you had created.

Please access your account using the following link: Access Your Account.

If you have any questions, please feel free to contact us for further clarification.


Best regards,

FCSC Logo `, userName, logoURL) } else if newStatus == "Denied" { subject = "Registration Update – FCSC" body = fmt.Sprintf(`

Dear %s,

Thank you for registering with FCSC. 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.


Best regards,

FCSC Logo `, userName, logoURL) } 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 }