package v1 import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" applogger "github.com/gochat/gochat/pkg/logger" "github.com/gochat/gochat/pkg/pagination" "github.com/gochat/gochat/pkg/response" ) // CategoryHandler handles Category CRUD endpoints. type CategoryHandler struct { svc *service.CategoryService } func NewCategoryHandler(svc *service.CategoryService) *CategoryHandler { return &CategoryHandler{svc: svc} } // Create creates a new category. // POST /api/v1/accounts/:account_id/portals/:portal_id/categories func (h *CategoryHandler) Create(c *gin.Context) { portalID, err := strconv.ParseUint(c.Param("portal_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid portal_id") return } accountID, err := strconv.ParseUint(c.Param("account_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } // Chatwoot: params.require(:category) → {"category": {...}} var wrapper struct { Category service.CreateCategoryRequest `json:"category"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.Category category, err := h.svc.Create(c.Request.Context(), uint(portalID), uint(accountID), &req) if err != nil { applogger.L().Errorf("Create category: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to create category") return } response.Created(c, category) } // Get retrieves a category by ID. // GET /api/v1/accounts/:account_id/portals/:portal_id/categories/:category_id func (h *CategoryHandler) Get(c *gin.Context) { categoryID, err := strconv.ParseUint(c.Param("category_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid category_id") return } category, err := h.svc.GetByID(c.Request.Context(), uint(categoryID)) if err != nil { applogger.L().Errorf("Get category: %v", err) response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "category not found") return } response.OK(c, category) } // Update modifies an existing category. // PUT /api/v1/accounts/:account_id/portals/:portal_id/categories/:category_id func (h *CategoryHandler) Update(c *gin.Context) { categoryID, err := strconv.ParseUint(c.Param("category_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid category_id") return } // Chatwoot: params.require(:category) → {"category": {...}} var wrapper struct { Category service.UpdateCategoryRequest `json:"category"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.Category category, err := h.svc.Update(c.Request.Context(), uint(categoryID), &req) if err != nil { applogger.L().Errorf("Update category: %v", err) response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "failed to update category") return } response.OK(c, category) } // Delete removes a category. // DELETE /api/v1/accounts/:account_id/portals/:portal_id/categories/:category_id func (h *CategoryHandler) Delete(c *gin.Context) { categoryID, err := strconv.ParseUint(c.Param("category_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid category_id") return } if err := h.svc.Delete(c.Request.Context(), uint(categoryID)); err != nil { applogger.L().Errorf("Delete category: %v", err) response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "failed to delete category") return } response.NoContent(c) } // List returns all categories for a portal. // GET /api/v1/accounts/:account_id/portals/:portal_id/categories func (h *CategoryHandler) List(c *gin.Context) { portalID, err := strconv.ParseUint(c.Param("portal_id"), 10, 64) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid portal_id") return } locale := c.Query("locale") pg := pagination.Parse(c) categories, count, err := h.svc.ListByPortalID(c.Request.Context(), uint(portalID), locale, pg.Page, pg.PerPage) if err != nil { applogger.L().Errorf("List categories: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to list categories") return } response.OKWithMeta(c, categories, pg.Page, pg.PerPage, count) } // Reorder updates the display order of categories. // POST /api/v1/accounts/:account_id/portals/:portal_id/categories/reorder func (h *CategoryHandler) Reorder(c *gin.Context) { var req reorderRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } positions := make(map[uint]int, len(req.Positions)) for _, entry := range req.Positions { positions[entry.ID] = entry.Position } if err := h.svc.Reorder(c.Request.Context(), positions); err != nil { applogger.L().Errorf("Reorder categories: %v", err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to reorder categories") return } response.NoContent(c) }