1510 lines
40 KiB
Go
1510 lines
40 KiB
Go
package controllers
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"nearle/db"
|
|
"nearle/domain"
|
|
"nearle/models"
|
|
"nearle/utils"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
// "os"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/google"
|
|
)
|
|
|
|
func GetApplocations(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
|
|
result := domain.GetApplocations(aid)
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetApplocationsv2(c *fiber.Ctx) error {
|
|
|
|
userid, _ := strconv.Atoi(c.Query("userid"))
|
|
|
|
result := domain.GetApplocationsv2(userid)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func GetApplocationConfig(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
|
|
result := domain.GetApplocationConfig(aid)
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetApplocationadmins(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
|
|
result := domain.GetApplocationadmins(aid)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func GetAppPricing(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
cid, _ := strconv.Atoi(c.Query("configid"))
|
|
pid, _ := strconv.Atoi(c.Query("pricingtypeid"))
|
|
|
|
result := domain.GetAppPricing(aid, cid, pid)
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetAllAppPricing(c *fiber.Ctx) error {
|
|
|
|
aid, _ := strconv.Atoi(c.Query("applocationid"))
|
|
|
|
result := domain.GetAllAppPricing(aid)
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func CreateAppPricing(c *fiber.Ctx) error {
|
|
|
|
var data models.Apppricing
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := domain.CreateAppProcing(data)
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetAppConfig(c *fiber.Ctx) error {
|
|
|
|
cid, _ := strconv.Atoi(c.Query("configid"))
|
|
|
|
result := domain.GetAppconfig(cid)
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
func GetAllAppConfig(c *fiber.Ctx) error {
|
|
|
|
result := domain.GetAllAppconfig()
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetApptypes(c *fiber.Ctx) error {
|
|
|
|
tag := c.Query("tag")
|
|
|
|
result := domain.GetApptypes(tag)
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetSubcategories(c *fiber.Ctx) error {
|
|
mid, _ := strconv.Atoi(c.Query("moduleid")) // 0 means all modules
|
|
cid, _ := strconv.Atoi(c.Query("categoryid")) // 0 means all categories
|
|
|
|
result := domain.GetSubcategories(mid, cid)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
func GetCategories(c *fiber.Ctx) error {
|
|
mid, _ := strconv.Atoi(c.Query("moduleid", "0")) // default to 0
|
|
|
|
result := domain.GetCategories(mid)
|
|
|
|
// Ensure result is never null
|
|
if result == nil {
|
|
result = []models.AppCategory{}
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
const (
|
|
fcmURL = "https://fcm.googleapis.com/v1/projects/nearle-gear/messages:send"
|
|
scope = "https://www.googleapis.com/auth/firebase.messaging"
|
|
serviceAcc = "nearle-gear-firebase-adminsdk-l9oha-23ca3b3609.json" // Path to your service account JSON
|
|
)
|
|
|
|
var client *http.Client
|
|
|
|
// Structure of FCM message
|
|
|
|
// GetClientFromServiceAccount reads the service account JSON file and generates a new token
|
|
func GetClientFromServiceAccount() (*http.Client, error) {
|
|
data, err := ioutil.ReadFile(serviceAcc)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read service account file: %v", err)
|
|
}
|
|
|
|
config, err := google.JWTConfigFromJSON(data, scope)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse service account JSON: %v", err)
|
|
}
|
|
|
|
// Use the token source to generate an HTTP client that automatically refreshes the token
|
|
client := config.Client(oauth2.NoContext)
|
|
return client, nil
|
|
}
|
|
|
|
func sendFCMMessage(token, title, body string) error {
|
|
if client == nil {
|
|
var err error
|
|
client, err = GetClientFromServiceAccount()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get client: %v", err)
|
|
}
|
|
}
|
|
|
|
notification := Notification{
|
|
Title: title,
|
|
Body: body,
|
|
}
|
|
|
|
fcmRequest := FCMRequestBody{}
|
|
fcmRequest.Message.Notification = notification
|
|
fcmRequest.Message.Token = token
|
|
|
|
requestBody, err := json.Marshal(fcmRequest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest("POST", fcmURL, bytes.NewBuffer(requestBody))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
return fmt.Errorf("failed to send message: %s", string(bodyBytes))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NotifyAdmin(c *fiber.Ctx) error {
|
|
body := new(struct {
|
|
Token []string `json:"token"`
|
|
Notification struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Sound string `json:"sound"`
|
|
Type string `json:"type"`
|
|
} `json:"notification"`
|
|
})
|
|
|
|
if err := c.BodyParser(body); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
|
}
|
|
|
|
if len(body.Token) == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "No tokens provided")
|
|
}
|
|
|
|
// Only send FCM to provided tokens
|
|
var successCount, failureCount int
|
|
for _, token := range body.Token {
|
|
err := sendFCMMessage(token, body.Notification.Title, body.Notification.Body)
|
|
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Failed to send to %s: %v", token, err)
|
|
failureCount++
|
|
} else {
|
|
utils.Logger.Infof("✅ Notification sent to %s", token)
|
|
successCount++
|
|
}
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"message": "Admin notifications sent",
|
|
"success": successCount,
|
|
"failure": failureCount,
|
|
})
|
|
}
|
|
|
|
func NotifyTenant(c *fiber.Ctx) error {
|
|
|
|
body := new(struct {
|
|
Token []string `json:"token"`
|
|
TenantID int `json:"tenantid"`
|
|
ModuleID int `json:"moduleid"`
|
|
LocationID int `json:"locationid"`
|
|
CustomerID int `json:"customerid"`
|
|
Notification struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Sound string `json:"sound"`
|
|
Type string `json:"type"`
|
|
} `json:"notification"`
|
|
})
|
|
|
|
if err := c.BodyParser(body); err != nil {
|
|
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body")
|
|
}
|
|
|
|
if len(body.Token) == 0 {
|
|
return fiber.NewError(fiber.StatusBadRequest, "No tokens provided")
|
|
}
|
|
|
|
successCount := 0
|
|
failureCount := 0
|
|
|
|
// Loop through each token and send notification + insert DB log
|
|
for _, token := range body.Token {
|
|
|
|
err := sendFCMMessage(token, body.Notification.Title, body.Notification.Body)
|
|
|
|
successCode := 0
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Failed to send to %s: %v", token, err)
|
|
failureCount++
|
|
} else {
|
|
utils.Logger.Infof("✅ Notification sent to %s", token)
|
|
successCount++
|
|
successCode = 1
|
|
}
|
|
|
|
// Insert into tenantnotifications table
|
|
insertQuery := `
|
|
INSERT INTO tenantnotifications
|
|
(title, message, tenantid, moduleid, locationid, customerid, notificationdate, successcode)
|
|
VALUES (?, ?, ?, ?, ?, ?, NOW(), ?)
|
|
`
|
|
|
|
if dbErr := db.DB.Exec(
|
|
insertQuery,
|
|
body.Notification.Title,
|
|
body.Notification.Body,
|
|
body.TenantID,
|
|
body.ModuleID,
|
|
body.LocationID,
|
|
body.CustomerID,
|
|
successCode,
|
|
).Error; dbErr != nil {
|
|
utils.Logger.Errorf("❌ DB Insert Error: %v", dbErr)
|
|
}
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"message": "Tenant notifications processed",
|
|
"success": successCount,
|
|
"failure": failureCount,
|
|
})
|
|
}
|
|
|
|
func GetTenantNotifications(c *fiber.Ctx) error {
|
|
tenantid, _ := strconv.Atoi(c.Query("tenantid"))
|
|
locationid, _ := strconv.Atoi(c.Query("locationid"))
|
|
customerid, _ := strconv.Atoi(c.Query("customerid"))
|
|
moduleid, _ := strconv.Atoi(c.Query("moduleid"))
|
|
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
|
|
if pageno == 0 {
|
|
pageno = 1
|
|
}
|
|
if pagesize == 0 {
|
|
pagesize = 10
|
|
}
|
|
|
|
result, _ := domain.GetTenantNotifications(
|
|
tenantid, locationid, customerid, moduleid,
|
|
pageno, pagesize,
|
|
)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Successful",
|
|
"details": result,
|
|
})
|
|
}
|
|
|
|
type Notification struct {
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
}
|
|
|
|
type FCMRequestBody struct {
|
|
Message struct {
|
|
Notification Notification `json:"notification"`
|
|
Token string `json:"token"`
|
|
} `json:"message"`
|
|
}
|
|
|
|
type WebhookPayload struct {
|
|
ID string `json:"id"`
|
|
Event string `json:"event"`
|
|
Timestamp string `json:"timestamp"`
|
|
Vendor string `json:"vendor"`
|
|
FailedAttempts int `json:"failedAttempts"`
|
|
WebhookID string `json:"webhookId"`
|
|
Payload interface{} `json:"payload"`
|
|
Context interface{} `json:"context"`
|
|
}
|
|
|
|
func WebhookHandler(c *fiber.Ctx) error {
|
|
utils.Info("➡️ Incoming webhook request")
|
|
|
|
var webhook WebhookPayload
|
|
if err := c.BodyParser(&webhook); err != nil {
|
|
utils.Logger.Errorf("❌ Failed to parse webhook JSON: %v", err)
|
|
utils.Logger.Errorf("Request body: %s", string(c.Body()))
|
|
return c.Status(fiber.StatusBadRequest).SendString("Invalid JSON")
|
|
}
|
|
|
|
utils.Logger.Infof("✅ Received event: %s at %s", webhook.Event, webhook.Timestamp)
|
|
utils.Logger.Infof("🧾 Payload: %+v", webhook.Payload)
|
|
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|
|
|
|
func RegisterWebhookHandler(c *fiber.Ctx) error {
|
|
// Shopfront GraphQL API endpoint
|
|
url := "https://testshopfront.onshopfront.com/api/v2/graphql"
|
|
|
|
// Webhook registration mutation
|
|
payload := map[string]string{
|
|
"query": `
|
|
mutation {
|
|
registerWebhook(input: {
|
|
name: "Sale Webhook",
|
|
url: "https://jupiter.nearle.app/live/api/v1/utils/webhooks",
|
|
events: [sale_created]
|
|
}) {
|
|
id
|
|
name
|
|
url
|
|
events
|
|
active
|
|
}
|
|
}`,
|
|
}
|
|
|
|
// Marshal payload
|
|
body, _ := json.Marshal(payload)
|
|
|
|
// Create POST request
|
|
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiI2OTA5MmM3ZmY0YzgzNmExNTBmZWIyOGU3N2RkOWYzMDFjNjM4ZGQ5ODViOGExZjMzYjdhODY1MzFhMzQxNjM0NTQ2ZjMxMTY0M2JjMGUzNSIsImlhdCI6MTc1MzExMTY5NC44OTAyNjksIm5iZiI6MTc1MzExMTY5NC44OTAyNzQsImV4cCI6MTc1NDMyMTI5NC44Nzg2OTYsInN1YiI6IiIsInNjb3BlcyI6WyJjcmVhdGVfd2ViaG9va3MiXX0.WtE2eYES3MWdcvQZIu0AubIzUAv839-c_UoSUdCx5ez2Px03u-YZbCX114LxAhcLIZbQBiyaFB24pW3ojQf_RIdIvDnWPoMo7RWDDVYiHT9xHbe6Crht4ORkaYF2IOijXCYgDRgXT3whBNVldFhFAyfhcj9qVehJdmpRh5QGmt0PT9LWoy7XeFR_ksmOPjufvv1JZLITYilT7SijrkpSvDsqUBlCXj86ifIFY9q3A2zexb7GlSvdE_RmRegPr0kLA23i4J4uwo9BBPDaJaIGYIP4C6x5j9fYBOmf0v3cSt1teoVGBFedjrizH4qvWazDZl44OlwlE6QQYCXpmjgFWXtLeiI54JQ-tygCbxi7xB_O4v8oqj_G4aP-aM-PykC5ge11ekJuCJXhsLcJajgztcjVVz8Bmnh0U5nnRG9Qx0szTiuy07p6y73IN4gWfLFjRd16pryhPjAHzI5bzbRGG_Pd0M5NcT7krZkQGEDkpuNgDKni6GgLQBy4My_mskJXjTw9lYP5i2Okn9CywJ5qaPNRMWW8eiEpb8qwzXxS2IRQQSKnpkT88P1LcSUMBhermYq60KFuh1RKYG5jNoWo2xwCsNJygCCk4iwtAvlhBoAZiOPwYJcgSM6jPdwqoUuRys_tqs938zIuf9hTdDosm5WM9z1sxXX-nqSfLqFuN_M") // Replace with valid token
|
|
|
|
// ✅ Add debug logs
|
|
utils.Logger.Infof("Request body: %s", string(body))
|
|
utils.Logger.Infof("Authorization header: %s", req.Header.Get("Authorization"))
|
|
|
|
// Send the request
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
utils.Logger.Errorf("Failed to register webhook: %v", err)
|
|
return c.Status(500).SendString("Webhook registration failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
respBody, _ := io.ReadAll(resp.Body)
|
|
utils.Logger.Infof("Webhook registration response: %s", respBody)
|
|
|
|
return c.Status(resp.StatusCode).SendString(string(respBody))
|
|
}
|
|
|
|
type WebhookInput struct {
|
|
Topic string `json:"topic"`
|
|
DeliveryUrl string `json:"delivery_url"`
|
|
}
|
|
|
|
func Createwebhook(c *fiber.Ctx) error {
|
|
code := c.Query("code")
|
|
vendor := c.Query("vendor")
|
|
|
|
// ✅ Handle OAuth callback
|
|
if code != "" && vendor != "" {
|
|
utils.Logger.Infof("✅ Received OAuth Code: %s", code)
|
|
utils.Logger.Infof("✅ Vendor: %s", vendor)
|
|
|
|
// 🔁 Exchange code for access token
|
|
tokenResp, err := http.PostForm("https://"+vendor+".onshopfront.com/oauth/token", url.Values{
|
|
"client_id": []string{"kY3Dfvohj8BV4lEoUDKSEsyRGnlGpXi8"},
|
|
"client_secret": []string{"Qk2eUXHaveIFEMNTny0yvVB1aHWvIY5J7blKwdHx"},
|
|
"code": []string{code},
|
|
"grant_type": []string{"client_credentials"},
|
|
"redirect_uri": []string{"https://jupiter.nearle.app/live/api/v1/utils/createwebhook"},
|
|
})
|
|
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Token exchange error: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to exchange code for access token",
|
|
})
|
|
}
|
|
defer tokenResp.Body.Close()
|
|
|
|
var tokenData map[string]interface{}
|
|
json.NewDecoder(tokenResp.Body).Decode(&tokenData)
|
|
utils.Logger.Infof("🔐 Token response: %v", tokenData)
|
|
|
|
accessToken, ok := tokenData["access_token"].(string)
|
|
if !ok || accessToken == "" {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Access token missing in response",
|
|
})
|
|
}
|
|
|
|
// ✅ Send accessToken back or store it (in DB, session, etc.)
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"message": "OAuth successful",
|
|
"access_token": accessToken,
|
|
})
|
|
}
|
|
|
|
// ✅ Handle webhook registration
|
|
var input WebhookInput
|
|
if err := c.BodyParser(&input); err != nil {
|
|
utils.Logger.Errorf("❌ BodyParser Error: %v", err)
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
if input.Topic == "" || input.DeliveryUrl == "" {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Topic and Delivery URL are required",
|
|
})
|
|
}
|
|
|
|
payload := map[string]string{
|
|
"topic": input.Topic,
|
|
"delivery_url": input.DeliveryUrl,
|
|
}
|
|
jsonPayload, _ := json.Marshal(payload)
|
|
|
|
// ❗ Replace this with actual token from DB/session
|
|
accessToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiIwNmI3Y2M0YTA4YzY1NDhlMmYzNTU3Nzg0MDAyNDdmMjk0ZTliOTBkMzQ4MjNjODZkNjJhNTU4NjZhZWE5M2EwNTZlYjQ4ZjM2NTQ3MTE0YyIsImlhdCI6MTc1MzM0MjczNi42MDc2NTcsIm5iZiI6MTc1MzM0MjczNi42MDc2NjIsImV4cCI6MTc1NDU1MjMzNi41OTQ3MTksInN1YiI6IiIsInNjb3BlcyI6WyJjcmVhdGVfd2ViaG9va3MiXX0.jl4ln8Qv2apX4A_uxohS2Rqj6hShBQD1M2tGV-8fkUPrxg8anh-4aDEApFiaZuB8PkfereU5nPlMP_r5Hs5vWNTIgZyp6A-uwUp89c7NZyQng9aCfmRCN0yN0VeV4SWMKasp6uFacG-_MrzToQPtsYGGam7tG8j5apfcy3spXvJSC7CwwfRxL-Kpi1mJ-t272onWZblEhLrwiMbKc_6B5mVCV8TkCsY6YS8UDT5SC93X8_l2X3wjg2MP-fL9zUWz13qK4xHVDvqpx2mc0fG-ZI7oJpafC5j8GszVkobKqQTwMACuJSt5l6AgWcau4uPKglMIzUqapipSuu4jjKvQCQQAwLaL8Xc9Zy_vtMgGepdNEcuRn7uZqg1CMHCUKqvYWu-YU_4hWlzHVUu3A4SPcs5ZfxIj5D5smMffQb_2FQm4PThMlJKD4T697VLm0jKfDN0P8c3PINhiEpixJFh-njlCCNUXylO4SxG2Q8Yg_nBMLGuTevsSZaZLXd4_Zvsw3tZpoDOGWyZ0-fjBcbCvjmq_eY2S1emdwHYNhoFo8j0FJjKuEnwj0XylaQu6bt1PF8E_LQit9SK2umVMRAtb7ayocoz9iSi6NP3416aI7B97pUwqWKzy9or7LKtydAfzfuFCzlhuXf_iW3VhQR8QlFJtUuKIt7Nfu3gGAm6pk0"
|
|
|
|
req, err := http.NewRequest("POST", "https://testshopfront.onshopfront.com/api/v1/webhooks", bytes.NewBuffer(jsonPayload))
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Failed to create request: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to create webhook request",
|
|
})
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Error calling Shopfront API: %v", err)
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Error sending request to Shopfront",
|
|
})
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
utils.Logger.Infof("🔁 Shopfront Response: %s", string(body))
|
|
|
|
return c.Status(resp.StatusCode).Send(body)
|
|
}
|
|
|
|
func WebhookReceiver(c *fiber.Ctx) error {
|
|
var webhookData struct {
|
|
ID string `json:"id"`
|
|
Event string `json:"event"`
|
|
Timestamp string `json:"timestamp"`
|
|
Vendor string `json:"vendor"`
|
|
FailedAttempts int `json:"failedAttempts"`
|
|
WebhookID string `json:"webhookId"`
|
|
Payload map[string]interface{} `json:"payload"`
|
|
Context interface{} `json:"context"`
|
|
}
|
|
|
|
if err := c.BodyParser(&webhookData); err != nil {
|
|
utils.Logger.Errorf("❌ Error parsing webhook payload: %v", err)
|
|
return c.SendStatus(fiber.StatusBadRequest) // 400
|
|
}
|
|
|
|
// Log webhook info for debugging
|
|
utils.Info("🔔 Webhook received:")
|
|
utils.Logger.Infof("ID: %s", webhookData.ID)
|
|
utils.Logger.Infof("Event: %s", webhookData.Event)
|
|
utils.Logger.Infof("Timestamp: %s", webhookData.Timestamp)
|
|
utils.Logger.Infof("Vendor: %s", webhookData.Vendor)
|
|
utils.Logger.Infof("Payload: %v", webhookData.Payload)
|
|
|
|
// Return 200 OK with no body
|
|
return c.SendStatus(fiber.StatusOK)
|
|
}
|
|
|
|
func GetShopfrontOrders(c *fiber.Ctx) error {
|
|
url := "https://testshopfront.onshopfront.com/graphql"
|
|
accessToken := "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIzODUiLCJqdGkiOiI5NzMzZjk3ZjlmNjAxMWM4NDc4NDE3Y2JlN2MzM2VjZjM0YzJiNWUyNDMzNjBmNTliNTg3NzgzZWZkOGYxOGQ0NjQ3MjZmMjMzMzAwOTE2YSIsImlhdCI6MTc1MzQyOTE2Ni4zMzkxNiwibmJmIjoxNzUzNDI5MTY2LjMzOTE2NSwiZXhwIjoxNzU0NjM4NzY2LjMyMjUzOSwic3ViIjoiIiwic2NvcGVzIjpbIioiXX0.1ne-a29-wS_HUn3vVnuUA1ikXeXfLtz6AMktFfbj1Zc4L1ih_PJAHvzODbgErv9pR-6AucDyxH2ZlJSb7MAL-V93yDMGK672n4lxXNDcVqgZEGjCrn7WlyopLhZDhjKJMzRFn1CN4FkW2P31Jgl55W0Ff_prX13usLy_Cad8rNlnp15TaTgJed-m40hsOeWEjQDP_LJWQCHSf2wXNPx-tpRe1FEbh6QQNV4nT3TRqUQEhWvd_nQXiqnZaqY0q6Xbgq_Vn-laNNySIvtyISaoS0jxfrSF2856FBhF-WKIzMfQs1oeQioZL-2IH1QGKrbCJfV9Z4gTaGfVLV8ZAWp1XB3CmpDFgEJih_H76u1NvIXs8S343jq6klV1GKaqvuYfz8RfwtNx7mfP6mOwve3SkRSuHZOedzcgKjdETL8AWx7AT-6gbSdGX49ktqD0NbixzfBu2MGkBn6j7vMVgtNqfrYHcfZAP65gRlo9UGsKAvAFbAZnGoO-FHC3R9iSOM2llmLNg65OoaPazavOfnKhLWzk06gS3t_8HJenqTM_7tp6YsW-W57k5y5MzJYcHvXSplvG8VYUFEkRo6PeGq7cib_78IopAp3vgxODSaDa9ETkn2RHGLS8IyMK6pAP7OLpZyc6lxNgDqnZE3S2kAqdrOgvrld3NzP4hWpwA3dka8"
|
|
|
|
query := `{
|
|
"query": "query { orders(first: 10) { edges { node { id invoiceNumber orderDate status totalCost } } } }"
|
|
}`
|
|
|
|
req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(query)))
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Request creation failed: %v", err)
|
|
return c.Status(500).JSON(fiber.Map{"error": "Request creation failed"})
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("Authorization", "Bearer "+accessToken)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Request failed: %v", err)
|
|
return c.Status(500).JSON(fiber.Map{"error": "Request execution failed"})
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
return c.Status(resp.StatusCode).Send(body)
|
|
}
|
|
|
|
func RegisterInventoryWebhook(c *fiber.Ctx) error {
|
|
// Log or process the incoming payload
|
|
var payload map[string]interface{}
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"error": "Invalid payload",
|
|
})
|
|
}
|
|
utils.Logger.Infof("📦 Inventory Webhook Received: %v", payload)
|
|
|
|
// Always return 2xx status with no body
|
|
return c.SendStatus(fiber.StatusNoContent) // 204
|
|
}
|
|
|
|
func GetAppModule(c *fiber.Ctx) error {
|
|
|
|
result := domain.GetAppModule()
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": result,
|
|
})
|
|
|
|
}
|
|
|
|
func GetUserBonusSummary(c *fiber.Ctx) error {
|
|
|
|
userid, err := strconv.Atoi(c.Query("userid"))
|
|
if err != nil || userid == 0 {
|
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": 400,
|
|
"message": "Valid userid is required",
|
|
})
|
|
}
|
|
|
|
data, err := domain.GetUserBonusSummary(userid)
|
|
if err != nil {
|
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": 500,
|
|
"message": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.Status(fiber.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": 200,
|
|
"message": "Success",
|
|
"details": data,
|
|
})
|
|
}
|
|
|
|
func CreateAppLocationConfig(c *fiber.Ctx) error {
|
|
|
|
var data models.AppLocationConfigRequest
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := domain.CreateAppLocationConfig(data)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": result,
|
|
})
|
|
}
|
|
|
|
func UpdateAppLocationConfig(c *fiber.Ctx) error {
|
|
|
|
var data models.AppLocationConfigRequest
|
|
|
|
if err := c.BodyParser(&data); err != nil {
|
|
return err
|
|
}
|
|
|
|
result := domain.UpdateAppLocationConfig(data)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": result,
|
|
})
|
|
}
|
|
|
|
func GetUserRoles(c *fiber.Ctx) error {
|
|
|
|
roles, err := domain.GetUserRoles()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"success": false,
|
|
"message": "Failed to fetch roles",
|
|
"error": err.Error(),
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"code": 200,
|
|
"message": "Success",
|
|
"details": roles,
|
|
})
|
|
}
|
|
|
|
// ============================================================================
|
|
// REDIS USER STORAGE ENDPOINTS
|
|
// ============================================================================
|
|
|
|
// StoreUserRedis - POST /api/v1/utils/users/redis
|
|
// Store user data in Redis with ZSET index and HASH storage
|
|
func CreateUserRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
// Parse request body
|
|
var userData struct {
|
|
UserID int `json:"userid"`
|
|
Username string `json:"username"`
|
|
FirstName string `json:"firstname"`
|
|
LastName string `json:"lastname"`
|
|
ContactNo string `json:"contactno"`
|
|
UserFCMToken string `json:"userfcmtoken"`
|
|
AppVersion string `json:"app_version"`
|
|
DeviceInfo string `json:"device_info"`
|
|
}
|
|
|
|
if err := c.BodyParser(&userData); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Validate required fields
|
|
if userData.UserID == 0 || userData.Username == "" || userData.FirstName == "" || userData.LastName == "" {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "userid, username, firstname, and lastname are required",
|
|
})
|
|
}
|
|
|
|
// Check Redis connection
|
|
pong, err := db.Rdb.Ping(ctx).Result()
|
|
utils.Logger.Infof("[Redis] Ping: %s Error: %v", pong, err)
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis connection error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Redis connection error: " + err.Error(),
|
|
})
|
|
}
|
|
|
|
// Marshal user data to JSON
|
|
jsonData, err := json.Marshal(userData)
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ JSON marshal error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to serialize user data",
|
|
})
|
|
}
|
|
|
|
// Store in HASH: "user:{userid}"
|
|
userKey := fmt.Sprintf("user:%d", userData.UserID)
|
|
if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil {
|
|
utils.Logger.Errorf("❌ Redis SET error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to store user in Redis",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ User stored in HASH — key=%s", userKey)
|
|
|
|
// Add to ZSET with timestamp as score: "user" → {score: timestamp, member: userid}
|
|
now := time.Now().Unix()
|
|
zadd := db.Rdb.ZAdd(ctx, "user", redis.Z{
|
|
Score: float64(now),
|
|
Member: fmt.Sprintf("%d", userData.UserID),
|
|
})
|
|
if zadd.Err() != nil {
|
|
utils.Logger.Errorf("❌ Redis ZADD error: %v", zadd.Err())
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to index user in ZSET",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ User indexed in ZSET — key=user, member=%d, score=%d", userData.UserID, now)
|
|
|
|
return c.Status(http.StatusCreated).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusCreated,
|
|
"message": "User stored successfully in Redis",
|
|
"data": userData,
|
|
})
|
|
}
|
|
|
|
// GetUserRedis - GET /api/v1/utils/users/redis/:userid
|
|
// Retrieve user data from Redis by userid
|
|
func GetUserRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userIDStr := c.Query("userid")
|
|
pageStr := c.Query("page")
|
|
pageSizeStr := c.Query("pagesize")
|
|
|
|
// 🔹 CASE 1: Single user
|
|
if userIDStr != "" {
|
|
userID, err := strconv.Atoi(userIDStr)
|
|
if err != nil || userID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Valid userid parameter is required",
|
|
})
|
|
}
|
|
|
|
userKey := fmt.Sprintf("user:%d", userID)
|
|
|
|
userData, err := db.Rdb.Get(ctx, userKey).Result()
|
|
if err != nil {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusNotFound,
|
|
"message": "User not found",
|
|
})
|
|
}
|
|
|
|
var user map[string]interface{}
|
|
json.Unmarshal([]byte(userData), &user)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"data": user,
|
|
})
|
|
}
|
|
|
|
// 🔹 CASE 2: ALL users with pagination
|
|
|
|
var (
|
|
page int
|
|
pageSize int
|
|
err error
|
|
)
|
|
|
|
if pageStr != "" {
|
|
page, err = strconv.Atoi(pageStr)
|
|
if err != nil || page <= 0 {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "invalid page",
|
|
})
|
|
}
|
|
}
|
|
|
|
if pageSizeStr != "" {
|
|
pageSize, err = strconv.Atoi(pageSizeStr)
|
|
if err != nil || pageSize <= 0 {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "invalid pagesize",
|
|
})
|
|
}
|
|
}
|
|
|
|
var start, end int64
|
|
|
|
if page > 0 && pageSize > 0 {
|
|
offset := (page - 1) * pageSize
|
|
start = int64(offset)
|
|
end = int64(offset + pageSize - 1)
|
|
} else {
|
|
start = 0
|
|
end = -1 // fetch ALL
|
|
}
|
|
|
|
// ✅ Get user IDs from ZSET
|
|
userIDs, err := db.Rdb.ZRevRange(ctx, "user", start, end).Result()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to fetch users",
|
|
})
|
|
}
|
|
|
|
// Convert to keys
|
|
var keys []string
|
|
for _, id := range userIDs {
|
|
keys = append(keys, fmt.Sprintf("user:%s", id))
|
|
}
|
|
|
|
// ✅ Use MGET (fast)
|
|
values, err := db.Rdb.MGet(ctx, keys...).Result()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to fetch user data",
|
|
})
|
|
}
|
|
|
|
var users []map[string]interface{}
|
|
|
|
for _, val := range values {
|
|
if val == nil {
|
|
continue
|
|
}
|
|
|
|
var user map[string]interface{}
|
|
json.Unmarshal([]byte(val.(string)), &user)
|
|
|
|
users = append(users, user)
|
|
}
|
|
|
|
// ✅ total count (optional)
|
|
// total, _ := db.Rdb.ZCard(ctx, "user").Result()
|
|
|
|
return c.JSON(fiber.Map{
|
|
|
|
"code": http.StatusOK,
|
|
"data": users,
|
|
"message": "Success",
|
|
"status": true,
|
|
})
|
|
}
|
|
|
|
// UpdateUserRedis - PUT /api/v1/utils/users/redis/:userid
|
|
// Update user data in Redis
|
|
func UpdateUserRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userIDStr := c.Params("userid")
|
|
userID, err := strconv.Atoi(userIDStr)
|
|
if err != nil || userID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Valid userid parameter is required",
|
|
})
|
|
}
|
|
|
|
// Parse request body
|
|
var userData map[string]interface{}
|
|
if err := c.BodyParser(&userData); err != nil {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Check Redis connection
|
|
_, err = db.Rdb.Ping(ctx).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis connection error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Redis connection error",
|
|
})
|
|
}
|
|
|
|
// Check if user exists
|
|
userKey := fmt.Sprintf("user:%d", userID)
|
|
exists, err := db.Rdb.Exists(ctx, userKey).Result()
|
|
if err != nil || exists == 0 {
|
|
utils.Logger.Warnf("⚠️ User not found for update — key=%s", userKey)
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusNotFound,
|
|
"message": "User not found",
|
|
})
|
|
}
|
|
|
|
// Add userid to update data
|
|
userData["userid"] = userID
|
|
|
|
// Marshal updated data
|
|
jsonData, err := json.Marshal(userData)
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ JSON marshal error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to serialize user data",
|
|
})
|
|
}
|
|
|
|
// Update in HASH
|
|
if err := db.Rdb.Set(ctx, userKey, jsonData, 0).Err(); err != nil {
|
|
utils.Logger.Errorf("❌ Redis SET error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to update user in Redis",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ User updated in HASH — key=%s", userKey)
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "User updated successfully",
|
|
"data": userData,
|
|
})
|
|
}
|
|
|
|
// DeleteUserRedis - DELETE /api/v1/utils/users/redis/:userid
|
|
// Delete user from both HASH and ZSET
|
|
func DeleteUserRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userIDStr := c.Params("userid")
|
|
userID, err := strconv.Atoi(userIDStr)
|
|
if err != nil || userID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Valid userid parameter is required",
|
|
})
|
|
}
|
|
|
|
// Check Redis connection
|
|
_, err = db.Rdb.Ping(ctx).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis connection error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Redis connection error",
|
|
})
|
|
}
|
|
|
|
userKey := fmt.Sprintf("user:%d", userID)
|
|
userIDStr = fmt.Sprintf("%d", userID)
|
|
|
|
// Delete from HASH
|
|
delResult, err := db.Rdb.Del(ctx, userKey).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis DEL error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to delete user",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ User deleted from HASH — key=%s, deleted=%d", userKey, delResult)
|
|
|
|
// Remove from ZSET
|
|
remResult, err := db.Rdb.ZRem(ctx, "user", userIDStr).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis ZREM error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to remove user from index",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ User removed from ZSET — key=user, member=%s, removed=%d", userIDStr, remResult)
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "User deleted successfully",
|
|
})
|
|
}
|
|
|
|
// GetAllUsersRedis - GET /api/v1/utils/users/redis
|
|
// Retrieve all users from ZSET
|
|
func GetAllUsersRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
// Check Redis connection
|
|
_, err := db.Rdb.Ping(ctx).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis connection error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Redis connection error",
|
|
})
|
|
}
|
|
|
|
// Get all user IDs from ZSET
|
|
userIDs, err := db.Rdb.ZRange(ctx, "user", 0, -1).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis ZRANGE error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to fetch users",
|
|
})
|
|
}
|
|
|
|
utils.Logger.Infof("✅ Retrieved %d user IDs from ZSET", len(userIDs))
|
|
|
|
// Fetch each user's data
|
|
var users []map[string]interface{}
|
|
for _, userIDStr := range userIDs {
|
|
userKey := fmt.Sprintf("user:%s", userIDStr)
|
|
userData, err := db.Rdb.Get(ctx, userKey).Result()
|
|
if err != nil {
|
|
utils.Logger.Warnf("⚠️ Failed to get user data — key=%s", userKey)
|
|
continue
|
|
}
|
|
|
|
var user map[string]interface{}
|
|
if err := json.Unmarshal([]byte(userData), &user); err != nil {
|
|
utils.Logger.Warnf("⚠️ Failed to unmarshal user data — key=%s", userKey)
|
|
continue
|
|
}
|
|
|
|
users = append(users, user)
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"count": len(users),
|
|
"data": users,
|
|
})
|
|
}
|
|
|
|
// UserExistsRedis - GET /api/v1/utils/users/redis/:userid/exists
|
|
// Check if user exists in Redis
|
|
func UserExistsRedis(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userIDStr := c.Params("userid")
|
|
userID, err := strconv.Atoi(userIDStr)
|
|
if err != nil || userID == 0 {
|
|
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusBadRequest,
|
|
"message": "Valid userid parameter is required",
|
|
})
|
|
}
|
|
|
|
// Check Redis connection
|
|
_, err = db.Rdb.Ping(ctx).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis connection error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Redis connection error",
|
|
})
|
|
}
|
|
|
|
userKey := fmt.Sprintf("user:%d", userID)
|
|
exists, err := db.Rdb.Exists(ctx, userKey).Result()
|
|
if err != nil {
|
|
utils.Logger.Errorf("❌ Redis EXISTS error: %v", err)
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to check user existence",
|
|
})
|
|
}
|
|
|
|
userExists := exists > 0
|
|
utils.Logger.Infof("✅ User existence check — userid=%d, exists=%v", userID, userExists)
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"exists": userExists,
|
|
})
|
|
}
|
|
|
|
func CreateRiderPeriodicLog(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
var log models.RiderLog
|
|
if err := c.BodyParser(&log); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
// Parse logdate
|
|
t, err := time.Parse("2006-01-02 15:04:05", log.LogDate)
|
|
if err != nil {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Invalid logdate format",
|
|
})
|
|
}
|
|
|
|
timestamp := t.Unix()
|
|
|
|
// Key
|
|
logKey := fmt.Sprintf("rider_periodic_log:%d:%d", log.UserID, timestamp)
|
|
|
|
data, _ := json.Marshal(log)
|
|
|
|
// Store STRING
|
|
err = db.Rdb.Set(ctx, logKey, data, 0).Err()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to store log",
|
|
})
|
|
}
|
|
|
|
// ✅ Per-user ZSET
|
|
userZsetKey := fmt.Sprintf("rider_periodic_logs:%d", log.UserID)
|
|
err = db.Rdb.ZAdd(ctx, userZsetKey, redis.Z{
|
|
Score: float64(timestamp),
|
|
Member: logKey,
|
|
}).Err()
|
|
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to index user log",
|
|
})
|
|
}
|
|
|
|
// 🔥 ADD THIS (GLOBAL INDEX)
|
|
err = db.Rdb.ZAdd(ctx, "rider_periodic_logs_all", redis.Z{
|
|
Score: float64(timestamp),
|
|
Member: logKey,
|
|
}).Err()
|
|
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to index global log",
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"message": "Rider periodic log stored",
|
|
})
|
|
}
|
|
|
|
func GetRiderPeriodicLogs(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userID := c.Query("userid")
|
|
|
|
var keys []string
|
|
var err error
|
|
|
|
if userID != "" {
|
|
// ✅ USER specific (latest only)
|
|
zsetKey := fmt.Sprintf("rider_periodic_logs:%s", userID)
|
|
keys, err = db.Rdb.ZRevRange(ctx, zsetKey, 0, 0).Result()
|
|
} else {
|
|
// ✅ GLOBAL (latest only)
|
|
keys, err = db.Rdb.ZRevRange(ctx, "rider_periodic_logs_all", 0, 0).Result()
|
|
}
|
|
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to fetch logs",
|
|
})
|
|
}
|
|
|
|
if len(keys) == 0 {
|
|
return c.JSON(fiber.Map{
|
|
"code": 200,
|
|
"data": []interface{}{},
|
|
"message": "No logs found",
|
|
"status": true,
|
|
})
|
|
}
|
|
|
|
// 🔥 Get only one value
|
|
val, err := db.Rdb.Get(ctx, keys[0]).Result()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to fetch log data",
|
|
})
|
|
}
|
|
|
|
var log map[string]interface{}
|
|
json.Unmarshal([]byte(val), &log)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": 200,
|
|
"data": log,
|
|
"message": "Success",
|
|
"status": true,
|
|
})
|
|
}
|
|
|
|
func CreateRiderStatus(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
var status models.RiderStatus
|
|
if err := c.BodyParser(&status); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Invalid request body",
|
|
})
|
|
}
|
|
|
|
if status.UserID == 0 || status.Status == "" {
|
|
return c.Status(400).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "userid and status required",
|
|
})
|
|
}
|
|
|
|
key := fmt.Sprintf("rider_status:%d", status.UserID)
|
|
|
|
data, _ := json.Marshal(status)
|
|
|
|
// ✅ Store latest status
|
|
err := db.Rdb.Set(ctx, key, data, 0).Err()
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to store status",
|
|
})
|
|
}
|
|
|
|
// 🔥 ADD THIS (MISSING PART)
|
|
err = db.Rdb.ZAdd(ctx, "rider_status_all", redis.Z{
|
|
Score: float64(time.Now().Unix()),
|
|
Member: key,
|
|
}).Err()
|
|
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{
|
|
"status": false,
|
|
"message": "Failed to index status",
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"status": true,
|
|
"message": "Rider status updated",
|
|
})
|
|
}
|
|
|
|
func GetRiderStatus(c *fiber.Ctx) error {
|
|
ctx := context.Background()
|
|
|
|
userIDStr := c.Query("userid")
|
|
pageStr := c.Query("page")
|
|
pageSizeStr := c.Query("pagesize")
|
|
|
|
// 🔹 CASE 1: Single user
|
|
if userIDStr != "" {
|
|
key := fmt.Sprintf("rider_status:%s", userIDStr)
|
|
|
|
val, err := db.Rdb.Get(ctx, key).Result()
|
|
if err != nil {
|
|
return c.Status(http.StatusNotFound).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusNotFound,
|
|
"message": "Status not found",
|
|
"data": []interface{}{},
|
|
})
|
|
}
|
|
|
|
var data map[string]interface{}
|
|
json.Unmarshal([]byte(val), &data)
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"data": data,
|
|
})
|
|
}
|
|
|
|
// 🔹 CASE 2: ALL users (with optional pagination)
|
|
|
|
page, _ := strconv.Atoi(pageStr)
|
|
pageSize, _ := strconv.Atoi(pageSizeStr)
|
|
|
|
var start, end int64
|
|
|
|
if page > 0 && pageSize > 0 {
|
|
offset := (page - 1) * pageSize
|
|
start = int64(offset)
|
|
end = int64(offset + pageSize - 1)
|
|
} else {
|
|
start = 0
|
|
end = -1 // return ALL
|
|
}
|
|
|
|
// 🔥 Fetch all status keys from global ZSET
|
|
keys, err := db.Rdb.ZRevRange(ctx, "rider_status_all", start, end).Result()
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to fetch statuses",
|
|
"data": []interface{}{},
|
|
})
|
|
}
|
|
|
|
// 🔥 Fast fetch using MGET
|
|
values, err := db.Rdb.MGet(ctx, keys...).Result()
|
|
if err != nil {
|
|
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
|
"status": false,
|
|
"code": http.StatusInternalServerError,
|
|
"message": "Failed to fetch data",
|
|
"data": []interface{}{},
|
|
})
|
|
}
|
|
|
|
var result []map[string]interface{}
|
|
|
|
for _, val := range values {
|
|
if val == nil {
|
|
continue
|
|
}
|
|
|
|
var item map[string]interface{}
|
|
json.Unmarshal([]byte(val.(string)), &item)
|
|
|
|
result = append(result, item)
|
|
}
|
|
|
|
return c.Status(http.StatusOK).JSON(fiber.Map{
|
|
"status": true,
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"data": result,
|
|
})
|
|
}
|