package v1 import ( "net/http" "strconv" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/response" ) // AssignableAgentHandler handles endpoints for finding agents assignable to conversations. // Reference: Chatwoot app/controllers/api/v1/accounts/assignable_agents_controller.rb // Two routes: // 1. GET /api/v1/accounts/:account_id/assignable_agents (with inbox_ids[] query param) // 2. GET /api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents (single inbox) type AssignableAgentHandler struct { svc *service.AssignableAgentService } // NewAssignableAgentHandler creates a new AssignableAgentHandler. func NewAssignableAgentHandler(svc *service.AssignableAgentService) *AssignableAgentHandler { return &AssignableAgentHandler{svc: svc} } // List returns agents that can be assigned to conversations in the specified inbox(es). // Supports two modes: // 1. Standalone resource: GET /api/v1/accounts/:account_id/assignable_agents?inbox_ids[]=1&inbox_ids[]=2 // (Ref: Chatwoot resources :assignable_agents, only: [:index] — takes inbox_ids[] query param) // 2. Inbox member action: GET /api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents // (Ref: Chatwoot get :assignable_agents, on: :member on inboxes) // // 返回带workload信息的agent列表,按workload升序排序(最少conversations优先)。 func (h *AssignableAgentHandler) List(c *gin.Context) { if h.svc == nil { response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "assignable agent service unavailable") return } accountID := getAccountID(c) if accountID == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } // Determine inbox IDs from either path param or query param inboxIDs := []uint{} // Mode 2: inbox_id from URL path (GET /accounts/:account_id/inboxes/:inbox_id/assignable_agents) if inboxIDStr := c.Param("inbox_id"); inboxIDStr != "" { inboxID, parseErr := strconv.ParseUint(inboxIDStr, 10, 32) if parseErr != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid inbox_id") return } inboxIDs = append(inboxIDs, uint(inboxID)) } // Mode 1 & 2: additional inbox IDs from query param inbox_ids[] if queryInboxIDs, exists := c.GetQueryArray("inbox_ids[]"); exists { for _, qid := range queryInboxIDs { id, parseErr := strconv.ParseUint(qid, 10, 32) if parseErr == nil { // Avoid duplicate if path inbox_id matches query inbox_id isDup := false if len(inboxIDs) > 0 && uint(id) == inboxIDs[0] { isDup = true } if !isDup { inboxIDs = append(inboxIDs, uint(id)) } } } } agents, svcErr := h.svc.GetAssignableAgents(c.Request.Context(), accountID, inboxIDs) if svcErr != nil { response.AbortWithStatusError(c, http.StatusUnprocessableEntity, response.ErrInternal, "failed to list assignable agents") return } c.JSON(http.StatusOK, gin.H{"payload": serializeAssignableAgents(accountID, agents)}) } func serializeAssignableAgents(accountID uint, agents []service.AssignableAgentDTO) []map[string]any { payload := make([]map[string]any, 0, len(agents)) for i := range agents { agent := agents[i] availableName := agent.DisplayName if availableName == "" { availableName = agent.Name } provider := agent.Provider if provider == "" { provider = "email" } customRoleID := any(nil) if agent.CustomRoleID != 0 { customRoleID = agent.CustomRoleID } payload = append(payload, map[string]any{ "id": agent.ID, "account_id": accountID, "availability_status": agent.AvailabilityStatus, "auto_offline": agent.AutoOffline, "confirmed": agent.Confirmed, "email": agent.Email, "provider": provider, "available_name": availableName, "name": agent.Name, "role": agent.Role, "thumbnail": agent.AvatarURL, "custom_role_id": customRoleID, }) } return payload }