package handler import ( "bytes" "encoding/json" "strconv" "github.com/gin-gonic/gin" "apskel-pos-be/internal/constants" "apskel-pos-be/internal/contract" "apskel-pos-be/internal/models" "apskel-pos-be/internal/service" "apskel-pos-be/internal/util" ) // EnakGameCustomerHandler serves /customer/enakgame (docs/rfc-enakgame.md ยง11). type EnakGameCustomerHandler struct { service service.EnakGameCustomerService } func NewEnakGameCustomerHandler(s service.EnakGameCustomerService) *EnakGameCustomerHandler { return &EnakGameCustomerHandler{service: s} } // ListGames is GET /customer/enakgame/games. func (h *EnakGameCustomerHandler) ListGames(c *gin.Context) { const method = "EnakGameCustomerHandler::ListGames" customerID, ok := customerIDFromGin(c, method) if !ok { return } util.HandleResponse(c.Writer, c.Request, h.service.ListGames(c.Request.Context(), customerID), method) } // StartSession is POST /customer/enakgame/sessions. It requires Idempotency-Key. func (h *EnakGameCustomerHandler) StartSession(c *gin.Context) { const method = "EnakGameCustomerHandler::StartSession" customerID, ok := customerIDFromGin(c, method) if !ok { return } var req contract.StartGameSessionRequest if err := c.ShouldBindJSON(&req); err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MissingFieldErrorCode, constants.RequestEntity, "game_id is required"), }), method) return } util.HandleResponse(c.Writer, c.Request, h.service.StartSession(c.Request.Context(), customerID, &req, idempotencyKey(c)), method) } // ListSessions is GET /customer/enakgame/sessions?game_id=&status=&page=&limit=. func (h *EnakGameCustomerHandler) ListSessions(c *gin.Context) { const method = "EnakGameCustomerHandler::ListSessions" customerID, ok := customerIDFromGin(c, method) if !ok { return } var q models.GameSessionListQuery if !bindQuery(c, &q, method) { return } util.HandleResponse(c.Writer, c.Request, h.service.ListSessions(c.Request.Context(), customerID, q), method) } // GetSession is GET /customer/enakgame/sessions/:id. func (h *EnakGameCustomerHandler) GetSession(c *gin.Context) { const method = "EnakGameCustomerHandler::GetSession" customerID, ok := customerIDFromGin(c, method) if !ok { return } id, ok := pathID(c, "id", method) if !ok { return } util.HandleResponse(c.Writer, c.Request, h.service.GetSession(c.Request.Context(), customerID, id), method) } // CompleteSession is POST /customer/enakgame/sessions/:id/complete. The body is read // leniently on purpose: a reward amount, or anything else the backend does not take // from the client, is ignored rather than refused (P1). An empty body is a result // with nothing in it. func (h *EnakGameCustomerHandler) CompleteSession(c *gin.Context) { const method = "EnakGameCustomerHandler::CompleteSession" customerID, ok := customerIDFromGin(c, method) if !ok { return } id, ok := pathID(c, "id", method) if !ok { return } body, ok := rawBody(c, method) if !ok { return } var in models.GameSessionCompleteInput if len(bytes.TrimSpace(body)) > 0 { if err := json.Unmarshal(body, &in); err != nil { util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{ contract.NewResponseError(constants.MalformedFieldErrorCode, constants.RequestEntity, "score must be a whole number and outcome a text"), }), method) return } } util.HandleResponse(c.Writer, c.Request, h.service.CompleteSession(c.Request.Context(), customerID, id, in), method) } // ListVouchers is GET /customer/vouchers. func (h *EnakGameCustomerHandler) ListVouchers(c *gin.Context) { const method = "EnakGameCustomerHandler::ListVouchers" customerID, ok := customerIDFromGin(c, method) if !ok { return } util.HandleResponse(c.Writer, c.Request, h.service.ListVouchers(c.Request.Context(), customerID), method) } // RedeemVoucher is POST /customer/vouchers/:id/redeem. It requires the PIN // and Idempotency-Key; the body holds the PIN, so it is never logged. func (h *EnakGameCustomerHandler) RedeemVoucher(c *gin.Context) { const method = "EnakGameCustomerHandler::RedeemVoucher" customerID, ok := customerIDFromGin(c, method) if !ok { return } id, ok := pathID(c, "id", method) if !ok { return } var req contract.RedeemVoucherRequest if !bindPinRequest(c, &req, method) { return } util.HandleResponse(c.Writer, c.Request, h.service.RedeemVoucher(c.Request.Context(), customerID, id, &req, idempotencyKey(c), pinRequestInfo(c)), method) } // ListRedemptions is GET /customer/vouchers/redemptions?page=&limit=. func (h *EnakGameCustomerHandler) ListRedemptions(c *gin.Context) { const method = "EnakGameCustomerHandler::ListRedemptions" customerID, ok := customerIDFromGin(c, method) if !ok { return } page, _ := strconv.Atoi(c.Query("page")) limit, _ := strconv.Atoi(c.Query("limit")) util.HandleResponse(c.Writer, c.Request, h.service.ListRedemptions(c.Request.Context(), customerID, page, limit), method) }