package handler import ( "errors" "strconv" "accounting-app/pkg/api" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // SavingsPotHandler handles HTTP requests for savings pot operations // Feature: financial-core-upgrade // Validates: Requirements 10.1-10.8 type SavingsPotHandler struct { savingsPotService *service.SavingsPotService } // NewSavingsPotHandler creates a new SavingsPotHandler instance func NewSavingsPotHandler(savingsPotService *service.SavingsPotService) *SavingsPotHandler { return &SavingsPotHandler{ savingsPotService: savingsPotService, } } // DepositRequest represents the request body for deposit operation type DepositRequest struct { Amount float64 `json:"amount" binding:"required,gt=0"` } // WithdrawRequest represents the request body for withdraw operation type WithdrawRequest struct { Amount float64 `json:"amount" binding:"required,gt=0"` } // Deposit handles POST /api/savings-pot/:id/deposit // Deposits money into a savings pot from the parent account // Validates: Requirements 10.1, 10.3, 10.5 func (h *SavingsPotHandler) Deposit(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } savingsPotID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid savings pot ID") return } var req DepositRequest if err := c.ShouldBindJSON(&req); err != nil { api.ValidationError(c, "Invalid request body: "+err.Error()) return } result, err := h.savingsPotService.Deposit(userID.(uint), uint(savingsPotID), req.Amount) if err != nil { if errors.Is(err, service.ErrSavingsPotNotFound) { api.NotFound(c, "Savings pot not found") return } if errors.Is(err, service.ErrNotASavingsPot) { api.BadRequest(c, "Account is not a savings pot") return } if errors.Is(err, service.ErrInsufficientAvailableBalance) { api.BadRequest(c, "Insufficient available balance in parent account") return } if errors.Is(err, service.ErrInvalidDepositAmount) { api.BadRequest(c, "Deposit amount must be positive") return } api.InternalError(c, "Failed to deposit: "+err.Error()) return } api.Success(c, result) } // Withdraw handles POST /api/savings-pot/:id/withdraw // Withdraws money from a savings pot back to the parent account // Validates: Requirements 10.2, 10.4, 10.6 func (h *SavingsPotHandler) Withdraw(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } savingsPotID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid savings pot ID") return } var req WithdrawRequest if err := c.ShouldBindJSON(&req); err != nil { api.ValidationError(c, "Invalid request body: "+err.Error()) return } result, err := h.savingsPotService.Withdraw(userID.(uint), uint(savingsPotID), req.Amount) if err != nil { if errors.Is(err, service.ErrSavingsPotNotFound) { api.NotFound(c, "Savings pot not found") return } if errors.Is(err, service.ErrNotASavingsPot) { api.BadRequest(c, "Account is not a savings pot") return } if errors.Is(err, service.ErrInsufficientSavingsPotBalance) { api.BadRequest(c, "Insufficient balance in savings pot") return } if errors.Is(err, service.ErrInvalidWithdrawAmount) { api.BadRequest(c, "Withdraw amount must be positive") return } api.InternalError(c, "Failed to withdraw: "+err.Error()) return } api.Success(c, result) } // GetSavingsPot handles GET /api/savings-pot/:id // Returns savings pot details including progress // Validates: Requirements 10.7, 10.8 func (h *SavingsPotHandler) GetSavingsPot(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } savingsPotID, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid savings pot ID") return } result, err := h.savingsPotService.GetSavingsPot(userID.(uint), uint(savingsPotID)) if err != nil { if errors.Is(err, service.ErrSavingsPotNotFound) { api.NotFound(c, "Savings pot not found") return } if errors.Is(err, service.ErrNotASavingsPot) { api.BadRequest(c, "Account is not a savings pot") return } api.InternalError(c, "Failed to get savings pot: "+err.Error()) return } api.Success(c, result) } // RegisterRoutes registers all savings pot routes to the given router group func (h *SavingsPotHandler) RegisterRoutes(rg *gin.RouterGroup) { savingsPot := rg.Group("/savings-pot") { savingsPot.POST("/:id/deposit", h.Deposit) savingsPot.POST("/:id/withdraw", h.Withdraw) savingsPot.GET("/:id", h.GetSavingsPot) } }