package v1 import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/repository" "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" ) // AgentHandler handles agent CRUD + bulk_create endpoints. // Reference: Chatwoot app/controllers/api/v1/accounts/agents_controller.rb // An "agent" in Chatwoot is a User with an AccountUser membership in a specific account. type AgentHandler struct { svc *service.AgentService } // NewAgentHandler creates a new AgentHandler. func NewAgentHandler(svc *service.AgentService) *AgentHandler { return &AgentHandler{svc: svc} } // List returns all agents in the account. // GET /api/v1/accounts/:account_id/agents // Reference: Chatwoot agents_controller.rb#index func (h *AgentHandler) List(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } pg := pagination.Parse(c) agents, total, err := h.svc.List(c.Request.Context(), accountID, pg.Offset, pg.PerPage) if err != nil { applogger.L().Errorf("List agents for account %d: %v", accountID, err) handleServiceError(c, err) return } response.OKWithMeta(c, agents, pg.Page, pg.PerPage, total) } // Get returns a single agent by ID. // GET /api/v1/accounts/:account_id/agents/:id func (h *AgentHandler) Get(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } id, err := strconv.ParseUint(c.Param("agent_id"), 10, 32) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid agent id") return } agent, svcErr := h.svc.Get(c.Request.Context(), uint(id), accountID) if svcErr != nil { applogger.L().Errorf("Get agent %d for account %d: %v", id, accountID, svcErr) handleServiceError(c, svcErr) return } response.OK(c, agent) } // Create adds an agent to the account. // POST /api/v1/accounts/:account_id/agents // Reference: Chatwoot agents_controller.rb#create → AgentBuilder.new.perform func (h *AgentHandler) Create(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } userID := getUserID(c) if userID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated") return } // Chatwoot: params.require(:agent) → request body must be {"agent": {...}} var wrapper struct { Agent service.CreateAgentRequest `json:"agent"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.Agent // Chatwoot: validate_limit → can_add_agent? — returns 402 if limit exceeded canAdd, err := h.svc.CanAddAgent(c.Request.Context(), accountID) if err != nil { applogger.L().Errorf("Check agent limit for account %d: %v", accountID, err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } if !canAdd { response.AbortWithStatusError(c, http.StatusPaymentRequired, response.ErrPaymentRequired, "Account limit exceeded. Please purchase more licenses") return } agent, svcErr := h.svc.Create(c.Request.Context(), accountID, userID, req) if svcErr != nil { if errors.Is(svcErr, repository.ErrAlreadyMember) { response.AbortWithStatusError(c, http.StatusConflict, response.ErrConflict, svcErr.Error()) return } applogger.L().Errorf("Create agent for account %d: %v", accountID, svcErr) handleServiceError(c, svcErr) return } response.Created(c, agent) } // Update modifies agent details (name on User, role/availability on AccountUser). // PUT /api/v1/accounts/:account_id/agents/:id // Reference: Chatwoot agents_controller.rb#update func (h *AgentHandler) Update(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } id, err := strconv.ParseUint(c.Param("agent_id"), 10, 32) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid agent id") return } // Chatwoot: params.require(:agent) → request body must be {"agent": {...}} var wrapper struct { Agent service.UpdateAgentRequest `json:"agent"` } if err := c.ShouldBindJSON(&wrapper); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } req := wrapper.Agent agent, svcErr := h.svc.Update(c.Request.Context(), uint(id), accountID, req) if svcErr != nil { applogger.L().Errorf("Update agent %d for account %d: %v", id, accountID, svcErr) handleServiceError(c, svcErr) return } response.OK(c, agent) } // Delete removes an agent from the account. // DELETE /api/v1/accounts/:account_id/agents/:id // Reference: Chatwoot agents_controller.rb#destroy → current_account_user.destroy! func (h *AgentHandler) Delete(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } id, err := strconv.ParseUint(c.Param("agent_id"), 10, 32) if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid agent id") return } svcErr := h.svc.Delete(c.Request.Context(), uint(id), accountID) if svcErr != nil { applogger.L().Errorf("Delete agent %d for account %d: %v", id, accountID, svcErr) handleServiceError(c, svcErr) return } response.OK(c, gin.H{"message": "agent removed"}) } // BulkCreate adds multiple agents to the account by email. // POST /api/v1/accounts/:account_id/agents/bulk_create // Reference: Chatwoot agents_controller.rb#bulk_create func (h *AgentHandler) BulkCreate(c *gin.Context) { accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "account not identified") return } userID := getUserID(c) if userID == 0 { response.AbortWithStatusError(c, http.StatusUnauthorized, response.ErrUnauthorized, "user not authenticated") return } var req service.BulkCreateAgentRequest if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } // Chatwoot: validate_limit_for_bulk_create — emails.count <= available_agent_count canAdd, err := h.svc.CanAddAgents(c.Request.Context(), accountID, len(req.Emails)) if err != nil { applogger.L().Errorf("Check agent limit for account %d: %v", accountID, err) response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, err.Error()) return } if !canAdd { response.AbortWithStatusError(c, http.StatusPaymentRequired, response.ErrPaymentRequired, "Account limit exceeded. Please purchase more licenses") return } agents, svcErr := h.svc.BulkCreate(c.Request.Context(), accountID, userID, req) if svcErr != nil { applogger.L().Errorf("BulkCreate agents for account %d: %v", accountID, svcErr) handleServiceError(c, svcErr) return } response.OK(c, agents) }