1639 lines
47 KiB
Go
1639 lines
47 KiB
Go
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", "kpi").
|
||
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.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)
|
||
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")
|
||
language := c.QueryParam("language")
|
||
langKey := "value_" + language
|
||
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)
|
||
}
|
||
|
||
cRes := utils.ProcessChartData(chartData, languageSourceConverted, langKey)
|
||
|
||
chartHeadingKey := "chart_heading_" + language
|
||
tabHeadingKey := "tab_heading_" + language
|
||
// 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(chartHeadingKey),
|
||
"tab_heading": record.GetString(tabHeadingKey),
|
||
"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, langKey)
|
||
if err != nil {
|
||
log.Fatalf("Error translating filters: %v", err)
|
||
}
|
||
|
||
aggregatedFilterResponse[kpi] = fRes
|
||
|
||
}
|
||
//--------------------
|
||
|
||
// Create the response structure
|
||
response := map[string]interface{}{
|
||
"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 {
|
||
|
||
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)
|
||
|
||
})
|
||
|
||
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(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering. Please click the link below to verify your email address:</p>
|
||
<p>
|
||
<a href="%s" style="padding: 10px 15px; background-color: blue; color: white; text-decoration: none; border-radius: 5px;">Verify Email</a>
|
||
</p>
|
||
<p>If you did not register for this account, please ignore this email.</p>
|
||
</br>
|
||
<p>Best regards,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
</body>
|
||
</html>
|
||
`, name, verificationURL, logoURL)
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: name,
|
||
Address: email,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send verification email to user: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Verification email sent successfully to the user.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
// Hook to send an email after a user is created
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
e.Router.GET("/api/custom/approve", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId")
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("reviewed") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Your Already Reviewed</h1>
|
||
<p>You have already been reviewed. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Update the status
|
||
newStatus := "Approved"
|
||
record.Set("verified", true)
|
||
record.Set("status", newStatus)
|
||
record.Set("reviewed", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Send the status change email
|
||
err = sendStatusChangeEmail(app, record, newStatus)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send approval email.",
|
||
})
|
||
}
|
||
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #e0f7fa;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #00796b;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #004d40;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #00796b;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<i>✔</i>
|
||
<h1>User Approved Successfully</h1>
|
||
<p>The user has been approved, and the approval email has been sent.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
|
||
// Approve Endpoint
|
||
e.Router.GET("/api/custom/reject", func(c echo.Context) error {
|
||
userId := c.QueryParam("userId")
|
||
if userId == "" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "Missing userId query parameter.",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("reviewed") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Your Already Reviewed</h1>
|
||
<p>You have already been reviewed. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Check if the status is already "Denied" to avoid duplicate email
|
||
if record.GetString("status") == "Denied" {
|
||
return c.JSON(400, map[string]interface{}{
|
||
"code": 400,
|
||
"message": "User is already rejected.",
|
||
})
|
||
}
|
||
|
||
// Update the status
|
||
newStatus := "Denied"
|
||
record.Set("verified", true)
|
||
record.Set("status", newStatus)
|
||
record.Set("reviewed", true)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to verify user.",
|
||
})
|
||
}
|
||
|
||
// Send the status change email
|
||
err = sendStatusChangeEmail(app, record, newStatus)
|
||
if err != nil {
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Failed to send rejection email.",
|
||
})
|
||
}
|
||
|
||
// Render a success message as an HTML response
|
||
return c.HTML(http.StatusOK, `
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>User Rejected</title>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
height: 100vh;
|
||
background-color: #ffe6e6;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
.message-box {
|
||
text-align: center;
|
||
color: #d9534f;
|
||
}
|
||
.message-box i {
|
||
font-size: 48px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.message-box p {
|
||
font-size: 18px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-box">
|
||
<i>❌</i>
|
||
<h1>User Rejected</h1>
|
||
<p>Rejection email sent successfully.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
})
|
||
|
||
return nil
|
||
})
|
||
|
||
// Serve static files from the provided public directory (if exists)
|
||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||
e.Router.GET("/verify-email", verifyEmailHandler)
|
||
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false))
|
||
return nil
|
||
})
|
||
|
||
//Feedback Mail Trigger
|
||
app.OnRecordAfterCreateRequest().Add(func(e *core.RecordCreateEvent) error {
|
||
if e.Record.Collection().Name == "feedback" {
|
||
// Retrieve feedback details
|
||
feedback := e.Record
|
||
userId := feedback.GetString("userId")
|
||
emojiRating := feedback.GetString("emoji_rating")
|
||
easeOfUse := feedback.GetString("ease_of_use")
|
||
quality := feedback.GetString("quality")
|
||
design := feedback.GetString("design")
|
||
redundancy := feedback.GetString("redundancy")
|
||
additionalFeedback := feedback.GetString("feedback")
|
||
|
||
if additionalFeedback == "" {
|
||
additionalFeedback = "No additional feedback provided."
|
||
}
|
||
|
||
// Fetch user details using userId
|
||
user, err := app.Dao().FindRecordById("users", userId)
|
||
if err != nil {
|
||
log.Printf("Error fetching user details: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
userName := user.GetString("uname")
|
||
|
||
// Extract and format the creation time
|
||
createdTime := e.Record.Get("created").(types.DateTime) // Extract created time
|
||
feedbackDateTime := createdTime.Time().UTC() // Convert to time.Time in UTC
|
||
|
||
// Format the date and time
|
||
formattedDate := feedbackDateTime.Format("02-01-2006") // DD-MM-YYYY
|
||
formattedDateTime := feedbackDateTime.Format("02.01.2006 15:04 UTC") // DD.MM.YYYY HH:mm UTC
|
||
|
||
// Admin details
|
||
adminName := "FCSC"
|
||
|
||
// Email subject
|
||
subject := "UAE Stats Feedback"
|
||
|
||
imageRecord, err := app.Dao().FindFirstRecordByData("images", "file_name", "app_logo")
|
||
if err != nil {
|
||
log.Printf("Failed to fetch logo from images collection: %v", err)
|
||
return nil
|
||
}
|
||
|
||
// Construct the logo URL
|
||
logoURL := fmt.Sprintf("%s/api/files/images/%s/%s",
|
||
baseUrl,
|
||
imageRecord.Id,
|
||
imageRecord.GetString("file"),
|
||
)
|
||
|
||
// Email body in HTML format
|
||
body := fmt.Sprintf(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p> You have received new feedback from a user through the mobile application.</p>
|
||
<p><b>User Details:</b></p>
|
||
<ul>
|
||
<li><b>Name</b> %s</li>
|
||
<li><b>Date of Submission:</b> %s</li>
|
||
<li><b>Time of Submission:</b> %s</li>
|
||
</ul>
|
||
<p><b>Feedback:</b></p>
|
||
<pre>
|
||
1. How was your experience with us today? Rating: %s
|
||
2. How did we perform in key areas?
|
||
1. Ease of Use: %s
|
||
2. Quality: %s
|
||
3. Design: %s
|
||
4. Redundancy: %s
|
||
3. Additional Feedback:
|
||
1. %s
|
||
</pre>
|
||
<p>Thank you,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
</body>
|
||
</html>
|
||
`, adminName, userName, formattedDate, formattedDateTime, emojiRating, easeOfUse, quality, design, redundancy, additionalFeedback, logoURL)
|
||
|
||
// Define the target type (e.g., "smtp")
|
||
targetType := "feedback_receive_mail"
|
||
|
||
// Fetch the email configuration record where type matches targetType
|
||
emailConfig, err := app.Dao().FindFirstRecordByData("email_configuration", "type", targetType)
|
||
if err != nil {
|
||
log.Printf("Failed to fetch email configuration for type '%s': %v", targetType, err)
|
||
return fmt.Errorf("email configuration not found for type '%s'", targetType)
|
||
}
|
||
|
||
// Get the email address
|
||
feedbackEmail := emailConfig.GetString("email")
|
||
if feedbackEmail == "" {
|
||
log.Printf("No email address configured for type '%s'", targetType)
|
||
return fmt.Errorf("no email address configured for type '%s'", targetType)
|
||
}
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress,
|
||
},
|
||
To: []mail.Address{
|
||
{
|
||
Name: adminName,
|
||
Address: feedbackEmail,
|
||
},
|
||
},
|
||
Subject: subject,
|
||
HTML: body,
|
||
}
|
||
|
||
// Send the email
|
||
err = app.NewMailClient().Send(message)
|
||
if err != nil {
|
||
log.Printf("Failed to send feedback email to admin: %v", err)
|
||
return err
|
||
}
|
||
|
||
log.Println("Feedback email sent successfully to the admin.")
|
||
}
|
||
return nil
|
||
})
|
||
|
||
app.OnRecordAfterCreateRequest("otp_requests").Add(func(e *core.RecordCreateEvent) error {
|
||
// Ensure the record has an email field
|
||
email, ok := e.Record.Get("email").(string)
|
||
if !ok || email == "" {
|
||
log.Println("Invalid or missing email field.")
|
||
return nil // or handle this case appropriately
|
||
}
|
||
|
||
// Generate the OTP
|
||
otp, err := generateOTP()
|
||
if err != nil {
|
||
log.Printf("Failed to generate OTP: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Calculate the expiration time (5 minutes from now)
|
||
expiresAt := time.Now().Add(5 * time.Minute)
|
||
|
||
// Create a mail.Address for the sender
|
||
sender := mail.Address{
|
||
Name: "FCSC",
|
||
Address: app.Settings().Meta.SenderAddress, // Ensure this is a valid email
|
||
}
|
||
|
||
// Create the email message
|
||
message := &mailer.Message{
|
||
From: sender,
|
||
To: []mail.Address{{Address: email}}, // Wrap the recipient email
|
||
Subject: "YOUR VERIFICATION CODE",
|
||
HTML: fmt.Sprintf(
|
||
`<p>Thanks for verifying your <strong>%s</strong> account!</p>
|
||
<p>Your code is: <strong>%d</strong></p>
|
||
<p>Sincerely,<br>Support team.</p>`,
|
||
email, otp,
|
||
),
|
||
}
|
||
|
||
// Send the email
|
||
if err := app.NewMailClient().Send(message); err != nil {
|
||
log.Printf("Failed to send email: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
// Update the record with the OTP
|
||
e.Record.Set("otp", fmt.Sprintf("%04d", otp))
|
||
e.Record.Set("expires_at", expiresAt) // Set the expiration time
|
||
|
||
if err := app.Dao().SaveRecord(e.Record); err != nil {
|
||
log.Printf("Failed to update record with OTP and expiration time: %v\n", err)
|
||
return err
|
||
}
|
||
|
||
log.Printf("OTP sent to %s expires at and saved successfully.\n", email)
|
||
return nil
|
||
})
|
||
|
||
// Start the PocketBase server
|
||
go func() {
|
||
if err := app.Start(); err != nil {
|
||
log.Fatal(err)
|
||
}
|
||
}()
|
||
|
||
// Start your custom HTTP server on port 8091
|
||
log.Println("Starting custom HTTP server on :8091")
|
||
if err := http.ListenAndServe(":8091", nil); err != nil {
|
||
log.Fatal("Failed to start server: ", err)
|
||
}
|
||
}
|
||
|
||
const baseUrl = "https://pb.venbait.in"
|
||
|
||
// generateOTP generates a random 4-digit OTP
|
||
func generateOTP() (int, error) {
|
||
max := big.NewInt(10000) // 4-digit numbers range: 0000 to 9999
|
||
n, err := rand.Int(rand.Reader, max)
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
return int(n.Int64()), nil
|
||
}
|
||
|
||
// Email verification handler
|
||
func verifyEmailHandler(c echo.Context) error {
|
||
log.Println("Starting verifyEmailHandler")
|
||
|
||
// Get the userId from the query parameter
|
||
userID := c.QueryParam("userId")
|
||
|
||
if userID == "" {
|
||
return c.JSON(http.StatusBadRequest, map[string]string{
|
||
"message": "User ID not provided",
|
||
})
|
||
}
|
||
|
||
// Check if app is properly initialized
|
||
if app == nil {
|
||
log.Fatal("PocketBase app is not initialized")
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||
"message": "Internal server error",
|
||
})
|
||
}
|
||
|
||
// Find the user record in PocketBase by userId
|
||
record, err := app.Dao().FindRecordById("users", userID)
|
||
if err != nil {
|
||
log.Printf("Error finding user: %v", err)
|
||
return c.JSON(500, map[string]interface{}{
|
||
"code": 500,
|
||
"message": "Error finding user.",
|
||
})
|
||
}
|
||
|
||
// Check if the email is already verified
|
||
if record.GetBool("user_mail_verify") {
|
||
return c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #ffe0e0;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #b71c1c;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #880e4f;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #b71c1c;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✖</div>
|
||
<h1>Email Already Verified</h1>
|
||
<p>Your email has already been verified. No further action is required.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
}
|
||
|
||
// Set the verified status to true
|
||
record.Set("user_mail_verify", true)
|
||
// log.Printf("Failed to verify user: %v", err)
|
||
if err := app.Dao().SaveRecord(record); err != nil {
|
||
log.Printf("Failed to update email verification status: %v", err)
|
||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||
"error": "Failed to update email verification status",
|
||
})
|
||
}
|
||
|
||
response := c.HTML(http.StatusOK, `
|
||
<html>
|
||
<head>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
background-color: #f0f8ff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 100vh;
|
||
margin: 0;
|
||
}
|
||
.message-container {
|
||
text-align: center;
|
||
background-color: #e0f7fa;
|
||
padding: 20px;
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||
}
|
||
.message-container h1 {
|
||
font-size: 24px;
|
||
color: #00796b;
|
||
margin-bottom: 10px;
|
||
}
|
||
.message-container p {
|
||
font-size: 16px;
|
||
color: #004d40;
|
||
}
|
||
.icon {
|
||
font-size: 50px;
|
||
color: #00796b;
|
||
margin-bottom: 20px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="message-container">
|
||
<div class="icon">✔</div>
|
||
<h1>Your registration is pending for Admin Approval</h1>
|
||
<p>Access will be granted once your account is approved.</p>
|
||
</div>
|
||
</body>
|
||
</html>
|
||
`)
|
||
|
||
// Fetch user details for email notification
|
||
username := record.GetString("uname")
|
||
userEmail := record.GetString("email")
|
||
|
||
// Send admin email asynchronously
|
||
go func() {
|
||
err = sendAdminEmail(app, userID, username, userEmail)
|
||
if err != nil {
|
||
log.Printf("Failed to send admin email: %v", err)
|
||
}
|
||
}()
|
||
|
||
return response
|
||
|
||
}
|
||
|
||
func sendAdminEmail(app *pocketbase.PocketBase, userID, username, userEmail string) error {
|
||
adminName := "Admin"
|
||
subject := "New User Registration Pending Review"
|
||
|
||
// Fetch the 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(`
|
||
<html>
|
||
<body>
|
||
<p>Dear %s,</p>
|
||
<p>A new user has registered and is awaiting your review.</p>
|
||
<p><strong>Details:</strong></p>
|
||
<p>Name: %s<br>
|
||
Email: %s</p>
|
||
<p>Please review the registration and take appropriate action:</p>
|
||
<p>
|
||
<a href="https://pb.venbait.in/api/custom/approve?userId=%s" style="padding: 10px 15px; background-color: green; color: white; text-decoration: none; border-radius: 5px;">Approve</a>
|
||
<a href="https://pb.venbait.in/api/custom/reject?userId=%s" style="padding: 10px 15px; background-color: red; color: white; text-decoration: none; border-radius: 5px;">Reject</a>
|
||
</p>
|
||
<p>Thank you,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
</body>
|
||
</html>
|
||
`, 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(`
|
||
<p>Dear %s,</p>
|
||
<p>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.</p>
|
||
<p>Please access your account using the following link: <a href="http://your-login-url.com">Login</a>.</p>
|
||
<p>If you have any questions, please feel free to contact us for further clarification.</p>
|
||
</br>
|
||
<p>Best regards,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
`, userName, logoURL)
|
||
} else if newStatus == "Denied" {
|
||
subject = "Registration Update – FCSC"
|
||
body = fmt.Sprintf(`
|
||
<p>Dear %s,</p>
|
||
<p>Thank you for registering with FCSC. After careful review, we regret to inform you that your registration has not been approved at this time.</p>
|
||
<p>If you have any questions or believe this decision was made in error, please feel free to contact us for further clarification.</p>
|
||
<p>We appreciate your understanding and thank you for your interest.</p>
|
||
</br>
|
||
<p>Best regards,</p>
|
||
<img src="%s" alt="FCSC Logo" style="width:150px; height:auto;">
|
||
`, 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
|
||
}
|