pocketbase/main.go

1595 lines
45 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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(`
<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">&#x2716;</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>&#x2714;</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">&#x2716;</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">&#x2716;</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">&#x2714;</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
}