package handler import ( "io" "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "apskel-pos-be/internal/appcontext" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/logger" "apskel-pos-be/internal/service" "apskel-pos-be/internal/util" ) // loyaltySettingsBodyLimit caps a settings body; a real one is well under 2 KB. const loyaltySettingsBodyLimit = 64 << 10 // LoyaltySettingsHandler serves the loyalty settings (docs/prd-point-coin.md F1, F2). type LoyaltySettingsHandler struct { loyaltySettingsService service.LoyaltySettingsService } func NewLoyaltySettingsHandler(loyaltySettingsService service.LoyaltySettingsService) *LoyaltySettingsHandler { return &LoyaltySettingsHandler{loyaltySettingsService: loyaltySettingsService} } func (h *LoyaltySettingsHandler) GetOutletSettings(c *gin.Context) { ctx := c.Request.Context() outletID, ok := parseUUIDParam(c, "outlet_id", "LoyaltySettingsHandler::GetOutletSettings") if !ok { return } response := h.loyaltySettingsService.GetOutletSettings(ctx, appcontext.FromGinContext(ctx), outletID) if response.HasErrors() { logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::GetOutletSettings -> service call failed") } util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::GetOutletSettings") } func (h *LoyaltySettingsHandler) UpdateOutletSettings(c *gin.Context) { ctx := c.Request.Context() outletID, ok := parseUUIDParam(c, "outlet_id", "LoyaltySettingsHandler::UpdateOutletSettings") if !ok { return } body, err := io.ReadAll(io.LimitReader(c.Request.Body, loyaltySettingsBodyLimit)) if err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "unable to read request body"), }), "LoyaltySettingsHandler::UpdateOutletSettings") return } response := h.loyaltySettingsService.UpdateOutletSettings(ctx, appcontext.FromGinContext(ctx), outletID, body) if response.HasErrors() { logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::UpdateOutletSettings -> service call failed") } util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::UpdateOutletSettings") } // parseUUIDParam reads a UUID path parameter, answering 400 itself when it is not one. func parseUUIDParam(c *gin.Context, name, method string) (uuid.UUID, bool) { id, err := uuid.Parse(c.Param(name)) if err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid "+name), }), method) return uuid.Nil, false } return id, true } func (h *LoyaltySettingsHandler) GetOrganizationSettings(c *gin.Context) { ctx := c.Request.Context() util.HandleResponse(c.Writer, c.Request, h.loyaltySettingsService.GetOrganizationSettings(ctx, appcontext.FromGinContext(ctx)), "LoyaltySettingsHandler::GetOrganizationSettings") } // UpdateOrganizationSettings is PUT /marketing/loyalty-settings. With ?dry_run=true it // saves nothing and returns what would change and what the balances in circulation // would be worth, for the warning the dashboard shows before saving (F2). func (h *LoyaltySettingsHandler) UpdateOrganizationSettings(c *gin.Context) { ctx := c.Request.Context() body, err := io.ReadAll(io.LimitReader(c.Request.Body, loyaltySettingsBodyLimit)) if err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "unable to read request body"), }), "LoyaltySettingsHandler::UpdateOrganizationSettings") return } response := h.loyaltySettingsService.UpdateOrganizationSettings(ctx, appcontext.FromGinContext(ctx), body, c.Query("dry_run") == "true") if response.HasErrors() { logger.FromContext(ctx).WithError(response.GetErrors()[0]).Error("LoyaltySettingsHandler::UpdateOrganizationSettings -> service call failed") } util.HandleResponse(c.Writer, c.Request, response, "LoyaltySettingsHandler::UpdateOrganizationSettings") } // ListHistory is GET /marketing/loyalty-settings/history, optionally for one outlet // (?outlet_id=). func (h *LoyaltySettingsHandler) ListHistory(c *gin.Context) { ctx := c.Request.Context() var outletID *uuid.UUID if raw := c.Query("outlet_id"); raw != "" { id, err := uuid.Parse(raw) if err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "Invalid outlet_id"), }), "LoyaltySettingsHandler::ListHistory") return } outletID = &id } page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) util.HandleResponse(c.Writer, c.Request, h.loyaltySettingsService.ListHistory(ctx, appcontext.FromGinContext(ctx), outletID, page, limit), "LoyaltySettingsHandler::ListHistory") }