package handler import ( "strconv" "accounting-app/pkg/api" "accounting-app/internal/service" "github.com/gin-gonic/gin" ) // BudgetHandler handles HTTP requests for budget operations type BudgetHandler struct { service *service.BudgetService } // NewBudgetHandler creates a new BudgetHandler instance func NewBudgetHandler(service *service.BudgetService) *BudgetHandler { return &BudgetHandler{ service: service, } } // RegisterRoutes registers budget-related routes func (h *BudgetHandler) RegisterRoutes(rg *gin.RouterGroup) { budgets := rg.Group("/budgets") { budgets.POST("", h.CreateBudget) budgets.GET("", h.GetAllBudgets) budgets.GET("/progress", h.GetAllBudgetProgress) budgets.GET("/:id", h.GetBudget) budgets.PUT("/:id", h.UpdateBudget) budgets.DELETE("/:id", h.DeleteBudget) budgets.GET("/:id/progress", h.GetBudgetProgress) } } // CreateBudget handles POST /api/v1/budgets func (h *BudgetHandler) CreateBudget(c *gin.Context) { var input service.BudgetInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, err.Error()) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } input.UserID = userID.(uint) budget, err := h.service.CreateBudget(input) if err != nil { switch err { case service.ErrInvalidBudgetAmount: api.BadRequest(c, err.Error()) case service.ErrInvalidDateRange: api.BadRequest(c, err.Error()) case service.ErrInvalidPeriodType: api.BadRequest(c, err.Error()) case service.ErrCategoryOrAccountRequired: api.BadRequest(c, err.Error()) default: api.InternalError(c, "Failed to create budget") } return } api.Created(c, budget) } // GetBudget handles GET /api/v1/budgets/:id func (h *BudgetHandler) GetBudget(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid budget ID") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } budget, err := h.service.GetBudget(userID.(uint), uint(id)) if err != nil { if err == service.ErrBudgetNotFound { api.NotFound(c, "Budget not found") return } api.InternalError(c, "Failed to get budget") return } api.Success(c, budget) } // GetAllBudgets handles GET /api/v1/budgets func (h *BudgetHandler) GetAllBudgets(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } budgets, err := h.service.GetAllBudgets(userID.(uint)) if err != nil { api.InternalError(c, "Failed to get budgets") return } api.Success(c, budgets) } // UpdateBudget handles PUT /api/v1/budgets/:id func (h *BudgetHandler) UpdateBudget(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid budget ID") return } var input service.BudgetInput if err := c.ShouldBindJSON(&input); err != nil { api.ValidationError(c, err.Error()) return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } input.UserID = userID.(uint) budget, err := h.service.UpdateBudget(userID.(uint), uint(id), input) if err != nil { switch err { case service.ErrBudgetNotFound: api.NotFound(c, "Budget not found") case service.ErrInvalidBudgetAmount: api.BadRequest(c, err.Error()) case service.ErrInvalidDateRange: api.BadRequest(c, err.Error()) case service.ErrInvalidPeriodType: api.BadRequest(c, err.Error()) case service.ErrCategoryOrAccountRequired: api.BadRequest(c, err.Error()) default: api.InternalError(c, "Failed to update budget") } return } api.Success(c, budget) } // DeleteBudget handles DELETE /api/v1/budgets/:id func (h *BudgetHandler) DeleteBudget(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid budget ID") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } err = h.service.DeleteBudget(userID.(uint), uint(id)) if err != nil { switch err { case service.ErrBudgetNotFound: api.NotFound(c, "Budget not found") case service.ErrBudgetInUse: api.Conflict(c, err.Error()) default: api.InternalError(c, "Failed to delete budget") } return } api.NoContent(c) } // GetBudgetProgress handles GET /api/v1/budgets/:id/progress // This endpoint returns the budget progress including warning flags (IsNearLimit, IsOverBudget) func (h *BudgetHandler) GetBudgetProgress(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { api.BadRequest(c, "Invalid budget ID") return } // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } progress, err := h.service.GetBudgetProgress(userID.(uint), uint(id)) if err != nil { if err == service.ErrBudgetNotFound { api.NotFound(c, "Budget not found") return } api.InternalError(c, "Failed to get budget progress") return } api.Success(c, progress) } // GetAllBudgetProgress handles GET /api/v1/budgets/progress // This endpoint returns progress for all active budgets including warning flags func (h *BudgetHandler) GetAllBudgetProgress(c *gin.Context) { // Get user ID from context userID, exists := c.Get("user_id") if !exists { api.Unauthorized(c, "User not authenticated") return } progressList, err := h.service.GetAllBudgetProgress(userID.(uint)) if err != nil { api.InternalError(c, "Failed to get budget progress") return } api.Success(c, progressList) }