package main import ( "crypto/rand" "encoding/json" "encoding/xml" "fmt" "io/ioutil" "log" "math/big" "net/http" "net/mail" "os" "pocketbase/utils" "strings" "time" "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/mailer" "github.com/pocketbase/pocketbase/tools/types" ) 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 FilterGroup struct { FilterKey string `json:"filter_key"` FilterData []interface{} `json:"filter_data"` } func generateFilters(responseRaw []map[string]interface{}, filterKeys []string) ([]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) } filters = append(filters, FilterGroup{ FilterKey: key, FilterData: filterData, }) } 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"` } // Query to get one row from the data_set_filter table err := app.DB(). Select("id", "full_data_url", "filter_keys", "file_status", "dataset"). 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 } // 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.DataSet) if err != nil { log.Printf("Failed to read JSON for dataset '%s': %v", dataSetFilter.DataSet, err) return nil, err } // Parse the filter keys filterKeys := strings.Split(dataSetFilter.FilterKeys, ",") // Generate filters filters, err := generateFilters(response, filterKeys) if err != nil { log.Printf("Failed to generate filters: %v", err) return nil, err } // Return the filters directly return filters, nil } // 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) } // 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 } translationMap[source.Value] = value } // Translate the filter data for i, filter := range filterGroups { 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 } var app *pocketbase.PocketBase func main() { const baseUrl = "https://pb.venbait.in" app = pocketbase.New() //app.Router.GET("/verify-email", verifyEmailHandler) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/api/custom/apicalltest", func(c echo.Context) error { // Get the database instance db := app.Dao().DB() // Define the struct for the expected result type Chart struct { Dataset string `json:"dataset"` Key string `json:"key"` Value string `json:"value"` ValueEn string `json:"value_en"` ValueAr string `json:"value_ar"` } // Query to fetch the data sqlQuery := "SELECT * FROM charts_variables" var results []Chart // Execute the query err := db.NewQuery(sqlQuery).All(&results) if err != nil { // Log the error details for better debugging log.Printf("Failed to execute query: %v", err) return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to execute query", "details": err.Error()}) } // Check if results are empty if len(results) == 0 { return c.JSON(http.StatusNotFound, map[string]string{"error": "No records found"}) } // Return the results return c.JSON(http.StatusOK, results) }) e.Router.GET("/api/getDataSet", func(c echo.Context) error { dataset := c.QueryParam("dataset") if dataset == "" { return c.JSON(http.StatusBadRequest, map[string]string{"error": "dataset is required"}) } // Fetch all matching records manually filter := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset}) records, err := app.Dao().FindRecordsByExpr("charts", filter) 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"}) } param := dbx.NewExp("dataset = {:dataset}", dbx.Params{"dataset": dataset}) 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()) } //get chart and card data 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) } // Retrieve the unset key and value keyToFilter := record.GetString("unset_key") valueToExclude := record.GetString("unset_value") // Check if both the key and value are empty if keyToFilter == "" || valueToExclude == "" { fmt.Println("unset_key or unset_value is empty. Skipping processing.") } else { // Log the extracted key and value for debugging purposes fmt.Printf("Filtering with Key: %s, Value: %s\n", keyToFilter, valueToExclude) // Filter the data dynamically and update chartData chartData = filterDynamicChartData(chartData, keyToFilter, valueToExclude) } langKey := "value_en" cRes := utils.ProcessChartData(chartData, languageSourceConverted, langKey) // Prepare the structure with URL and its 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"), "response": cRes, "group_by": record.GetString("group_by"), "chart_heading": record.GetString("chart_heading"), "unset_value": record.Get("unset_value"), } // 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, "value_en") if err != nil { log.Fatalf("Error translating filters: %v", err) } aggregatedFilterResponse[kpi] = fRes } temp, err := fetchfilterJSON(dataset) // Create the response structure response := map[string]interface{}{ "filter_data": temp, "new_filter_data": aggregatedFilterResponse, "data": aggregatedResponse, } 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 { // Define the struct for holding the query results mainTopics := []struct { MainTopic string `db:"main_topic" json:"main_topic"` 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", "main_topic_list_order", "color_pattern"). From("home_screen"). GroupBy("main_topic", "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"` }{} err := app.DB(). NewQuery("SELECT data_set, data_set_tile_heading, value_source, value, data_set_list_order FROM home_screen WHERE main_topic={:topic} "). Bind(dbx.Params{ "topic": mainTopic.MainTopic, }). All(&dataSets) if err != nil { log.Printf("Error fetching data sets: %v", err) continue } // Add the main topic and its sub-topics to the result result = append(result, map[string]interface{}{ "main_topic": mainTopic.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 { // Define the struct for holding the query results mainTopics := []struct { MainTopic string `db:"main_topic" json:"main_topic"` 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", "main_topic_list_order", "color_pattern"). From("uae_numbers_screen"). GroupBy("main_topic", "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 { SubTopic string `db:"sub_topic" json:"sub_topic"` 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", "sub_topic_list_order"). From("uae_numbers_screen"). Where(dbx.HashExp{"main_topic": mainTopic.MainTopic}). GroupBy("sub_topic"). OrderBy("sub_topic_list_order ASC"). All(&subTopics) if err != nil { log.Printf("Failed to fetch sub-topics for main topic %s: %v", mainTopic.MainTopic, 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"` }{} // Query to get data-set for the current sub topic err := app.DB(). Select("data_set", "data_set_tile_heading", "value_source", "value", "data_set_list_order"). From("uae_numbers_screen"). Where(dbx.HashExp{"sub_topic": subTopic.SubTopic}). OrderBy("data_set_list_order ASC"). All(&dataSets) if err != nil { log.Printf("Failed to fetch data-set for sub topic %s: %v", subTopic.SubTopic, err) continue } result2 = append(result2, map[string]interface{}{ "sub_topic": subTopic.SubTopic, "sub_topic_list_order": subTopic.SubTopicListOrder, "tile_data": dataSets, }) } // Add the main topic and its sub-topics to the result result = append(result, map[string]interface{}{ "main_topic": mainTopic.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) }) 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 := "Please Verify Your Email Address" // 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. Please click the link below to verify your email address:
If you did not register for this account, please ignore this email.
Best regards,
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,
Thanks for verifying your %s account!
Your code is: %d
Sincerely,
Support team.
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:
Thank you,
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: Login.
If you have any questions, please feel free to contact us for further clarification.
Best regards,
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,