package handlers import ( "fmt" "strconv" "time" "github.com/SamuelTariku/FortuneBet-Backend/internal/domain" "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response" "github.com/gofiber/fiber/v2" "go.uber.org/zap" ) // CreateShopBet godoc // @Summary Create bet at branch // @Description Create bet at branch // @Tags transaction // @Accept json // @Produce json // @Param createBet body domain.ShopBetReq true "create bet" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/bet [post] func (h *Handler) CreateShopBet(c *fiber.Ctx) error { userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) company_id := c.Locals("company_id").(domain.ValidInt64) var req domain.ShopBetReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CreateBetReq failed to parse request", zap.Any("request", req), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "invalid request body"+err.Error()) } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } return fiber.NewError(fiber.StatusBadRequest, errMsg) } shopBet, err := h.transactionSvc.CreateShopBet(c.Context(), userID, role, company_id, req) if err != nil { var statusCode int if isBetError := h.betSvc.CheckIfBetError(err); isBetError { statusCode = fiber.StatusBadRequest } else { statusCode = fiber.StatusInternalServerError } h.mongoLoggerSvc.Info("Failed to create shop bet", zap.Int64("userID", userID), zap.String("role", string(role)), zap.Int("status_code", statusCode), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(statusCode, "failed to create shop bet"+err.Error()) } res := domain.ConvertShopBet(shopBet) return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) } // CashoutBet godoc // @Summary Cashout bet at branch // @Description Cashout bet at branch // @Tags transaction // @Accept json // @Produce json // @Param createBet body domain.CashoutReq true "cashout bet" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/bet/{id} [get] func (h *Handler) GetShopBetByBetID(c *fiber.Ctx) error { betIDstr := c.Params("id") betID, err := strconv.ParseInt(betIDstr, 10, 64) if err != nil { h.mongoLoggerSvc.Info("GetShopBetByBetID failed bet id is invalid", zap.String("betID", betIDstr), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "bet ID is invalid:"+err.Error()) } bet, err := h.transactionSvc.GetShopBetByBetID(c.Context(), betID) if err != nil { h.mongoLoggerSvc.Error("GetShopBetByBetID failed invalid bet id", zap.Int64("betID", betID), zap.Int("status_code", fiber.StatusNotFound), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusNotFound, err.Error()) } res := domain.ConvertShopBetDetail(bet) return response.WriteJSON(c, fiber.StatusOK, "Shop bet fetched successfully", res, nil) } // CashoutBet godoc // @Summary Cashout bet at branch // @Description Cashout bet at branch // @Tags transaction // @Accept json // @Produce json // @Param cashoutBet body domain.CashoutReq true "cashout bet" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/bet/{id}/cashout [post] func (h *Handler) CashoutBet(c *fiber.Ctx) error { betIDStr := c.Params("id") betID, err := strconv.ParseInt(betIDStr, 10, 64) if err != nil { h.mongoLoggerSvc.Info("CashoutReq invalid bet id", zap.String("betID", betIDStr), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid bet id") } userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) var req domain.CashoutReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CashoutReq failed to parse request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Info("CashoutReq validation failed", zap.Any("request", req), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } transaction, err := h.transactionSvc.CashoutBet(c.Context(), betID, userID, role, req, companyID) if err != nil { h.mongoLoggerSvc.Error("Failed to cashout bet", zap.Int64("userID", userID), zap.Int64("betID", betID), zap.String("role", string(role)), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := domain.ConvertShopTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) } // CashoutByCashoutID godoc // @Summary Cashout bet by cashoutID // @Description Cashout bet by cashoutID // @Tags transaction // @Accept json // @Produce json // @Param cashoutBet body domain.CashoutReq true "cashout bet" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/cashout [post] func (h *Handler) CashoutByCashoutID(c *fiber.Ctx) error { userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) var req domain.CashoutReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CashoutReq failed to parse request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request") } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Info("CashoutReq validation failed", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.String("error", errMsg), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), req.CashoutID) if err != nil { h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id", zap.String("CashoutID", req.CashoutID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } transaction, err := h.transactionSvc.CashoutBet(c.Context(), bet.BetID, userID, role, req, companyID) if err != nil { h.mongoLoggerSvc.Info("Failed to cashout bet", zap.Int64("BetID", bet.BetID), zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } res := domain.ConvertShopTransaction(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) } // CashoutBet godoc // @Summary Cashout bet at branch // @Description Cashout bet at branch // @Tags transaction // @Accept json // @Produce json // @Param createBet body domain.CashoutReq true "cashout bet" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/cashout/{id} [get] func (h *Handler) GetShopBetByCashoutID(c *fiber.Ctx) error { cashoutID := c.Params("id") if cashoutID == "" { h.mongoLoggerSvc.Info("CashoutReq failed cashout id is required", zap.String("cashoutID", cashoutID), zap.Int("status_code", fiber.StatusBadRequest), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "cashout ID is required") } bet, err := h.transactionSvc.GetShopBetByCashoutID(c.Context(), cashoutID) if err != nil { h.mongoLoggerSvc.Info("CashoutReq failed invalid cashout id", zap.String("CashoutID", cashoutID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } res := domain.ConvertShopBetDetail(bet) return response.WriteJSON(c, fiber.StatusOK, "Transaction created successfully", res, nil) } // DepositForCustomer godoc // @Summary Shop deposit into customer wallet // @Description Transfers money from branch wallet to customer wallet // @Tags transaction // @Accept json // @Produce json // @Param transferToWallet body domain.ShopDepositReq true "ShopDepositReq" // @Success 200 {object} domain.ShopDepositRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/deposit [post] func (h *Handler) DepositForCustomer(c *fiber.Ctx) error { // Get sender ID from the cashier userID := c.Locals("user_id").(int64) role := c.Locals("role").(domain.Role) var req domain.ShopDepositReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("CreateTransferReq failed", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } valErrs, ok := h.validator.Validate(c, req) if !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Info("Failed to validate customer deposit", zap.Any("request", req), zap.Int("status_code", fiber.StatusBadRequest), zap.String("error", errMsg), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } deposit, err := h.transactionSvc.CreateShopDeposit(c.Context(), userID, role, req) if err != nil { h.mongoLoggerSvc.Info("failed to create shop deposit", zap.Int64("userID", userID), zap.String("role", string(role)), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := domain.ConvertShopDeposit(deposit) return response.WriteJSON(c, fiber.StatusOK, "Transfer Successful", res, nil) } // GetAllTransactions godoc // @Summary Gets all transactions // @Description Gets all the transactions // @Tags transaction // @Accept json // @Produce json // @Success 200 {array} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/transaction [get] func (h *Handler) GetAllTransactions(c *fiber.Ctx) error { // Get user_id from middleware // userID := c.Locals("user_id").(int64) // role := c.Locals("role").(domain.Role) companyID := c.Locals("company_id").(domain.ValidInt64) branchID := c.Locals("branch_id").(domain.ValidInt64) searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") var createdBefore domain.ValidTime if createdBeforeQuery != "" { createdBeforeParsed, err := time.Parse(time.RFC3339, createdBeforeQuery) if err != nil { h.mongoLoggerSvc.Info("invalid start_time format", zap.String("createdBefore", createdBeforeQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_before format") } createdBefore = domain.ValidTime{ Value: createdBeforeParsed, Valid: true, } } createdAfterQuery := c.Query("created_after") var createdAfter domain.ValidTime if createdAfterQuery != "" { createdAfterParsed, err := time.Parse(time.RFC3339, createdAfterQuery) if err != nil { h.mongoLoggerSvc.Info("invalid start_time format", zap.String("createdAfter", createdAfterQuery), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid created_after format") } createdAfter = domain.ValidTime{ Value: createdAfterParsed, Valid: true, } } // Check user role and fetch transactions accordingly transactions, err := h.transactionSvc.GetAllShopTransactions(c.Context(), domain.ShopTransactionFilter{ CompanyID: companyID, BranchID: branchID, Query: searchString, CreatedBefore: createdBefore, CreatedAfter: createdAfter, }) if err != nil { h.mongoLoggerSvc.Info("Failed to get transactions", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := make([]domain.ShopTransactionRes, len(transactions)) for i, transaction := range transactions { res[i] = domain.ConvertShopTransactionDetail(transaction) } return response.WriteJSON(c, fiber.StatusOK, "Transactions retrieved successfully", res, nil) } // GetTransactionByID godoc // @Summary Gets transaction by id // @Description Gets a single transaction by id // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/transaction/{id} [get] func (h *Handler) GetTransactionByID(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { h.mongoLoggerSvc.Info("Invalid transaction ID", zap.String("transactionID", transactionID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } transaction, err := h.transactionSvc.GetShopTransactionByID(c.Context(), id) if err != nil { h.mongoLoggerSvc.Error("Failed to get shop transaction by ID", zap.Int64("transactionID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to retrieve transaction") } res := domain.ConvertShopTransactionDetail(transaction) return response.WriteJSON(c, fiber.StatusOK, "Transaction retrieved successfully", res, nil) } // GetShopBetByTransactionID godoc // @Summary Gets shop bet by transaction id // @Description Gets a single shop bet by transaction id // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Success 200 {object} domain.ShopTransactionRes // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/transaction/{id}/bet [get] func (h *Handler) GetShopBetByTransactionID(c *fiber.Ctx) error { transactionID := c.Params("id") id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { h.mongoLoggerSvc.Info("Invalid transaction ID", zap.String("transactionID", transactionID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, err.Error()) } transaction, err := h.transactionSvc.GetShopBetByShopTransactionID(c.Context(), id) if err != nil { h.mongoLoggerSvc.Error("Failed to get transaction by ID", zap.Int64("transactionID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } res := domain.ConvertShopBetDetail(transaction) return response.WriteJSON(c, fiber.StatusOK, "Shop bet retrieved successfully", res, nil) } // UpdateTransactionVerified godoc // @Summary Updates the verified field of a transaction // @Description Updates the verified status of a transaction // @Tags transaction // @Accept json // @Produce json // @Param id path int true "Transaction ID" // @Param updateVerified body domain.UpdateTransactionVerifiedReq true "Updates Transaction Verification" // @Success 200 {object} response.APIResponse // @Failure 400 {object} response.APIResponse // @Failure 500 {object} response.APIResponse // @Router /api/v1/shop/transaction/{id} [put] func (h *Handler) UpdateTransactionVerified(c *fiber.Ctx) error { transactionID := c.Params("id") userID := c.Locals("user_id").(int64) companyID := c.Locals("company_id").(domain.ValidInt64) branchID := c.Locals("branch_id").(domain.ValidInt64) role := c.Locals("role").(domain.Role) id, err := strconv.ParseInt(transactionID, 10, 64) if err != nil { h.mongoLoggerSvc.Info("Invalid transaction ID", zap.String("transactionID", transactionID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid transaction ID") } var req domain.UpdateTransactionVerifiedReq if err := c.BodyParser(&req); err != nil { h.mongoLoggerSvc.Info("Failed to parse UpdateTransactionVerified request", zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusBadRequest, "Invalid request") } if valErrs, ok := h.validator.Validate(c, req); !ok { var errMsg string for field, msg := range valErrs { errMsg += fmt.Sprintf("%s: %s; ", field, msg) } h.mongoLoggerSvc.Info("Failed to validate UpdateTransactionVerified", zap.Int64("userID", userID), zap.Int("status_code", fiber.StatusBadRequest), zap.Error(err), zap.Time("timestamp", time.Now()), zap.String("errMsg", errMsg), ) return fiber.NewError(fiber.StatusBadRequest, errMsg) } err = h.transactionSvc.UpdateShopTransactionVerified(c.Context(), id, req.Verified, userID, role, companyID, branchID) if err != nil { h.mongoLoggerSvc.Error("Failed to update transaction verification", zap.Int64("transactionID", id), zap.Int("status_code", fiber.StatusInternalServerError), zap.Error(err), zap.Time("timestamp", time.Now()), ) return fiber.NewError(fiber.StatusInternalServerError, "Failed to update transaction verification") } return response.WriteJSON(c, fiber.StatusOK, "Transaction updated successfully", nil, nil) }