package v1 import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/service" ) // InboxMemberHandler handles inbox member (seat assignment) API endpoints. // Reference: Chatwoot app/controllers/api/v1/inbox_members_controller.rb // Routes manage which agents are assigned to which inboxes (seat assignment). type InboxMemberHandler struct { svc *service.InboxMemberService } // NewInboxMemberHandler creates a new InboxMemberHandler. func NewInboxMemberHandler(svc *service.InboxMemberService) *InboxMemberHandler { return &InboxMemberHandler{svc: svc} } // ListMembers retrieves all agents assigned to an inbox. // GET /api/v1/accounts/:id/inboxes/:inbox_id/members // Reference: Chatwoot inbox_members#index func (h *InboxMemberHandler) ListMembers(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } members, svcErr := h.svc.ListByInbox(c.Request.Context(), inboxID) if svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to list inbox members"}) return } c.JSON(http.StatusOK, gin.H{ "members": members, "meta": gin.H{"count": len(members)}, }) } // AddMember assigns an agent to an inbox (seat assignment). // POST /api/v1/accounts/:id/inboxes/:inbox_id/members // Reference: Chatwoot inbox_members#create func (h *InboxMemberHandler) AddMember(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } var req service.AddMemberRequest if bindErr := c.ShouldBindJSON(&req); bindErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) return } // Override inbox_id from URL path parameter req.InboxID = inboxID member, svcErr := h.svc.AddMember(c.Request.Context(), req) if svcErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": svcErr.Error()}) return } c.JSON(http.StatusCreated, member) } // RemoveMember removes an agent from an inbox (unassign seat). // DELETE /api/v1/accounts/:id/inboxes/:inbox_id/members/:user_id // Reference: Chatwoot inbox_members#destroy func (h *InboxMemberHandler) RemoveMember(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } userID, err := parseUintParam(c, "user_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) return } if svcErr := h.svc.RemoveMember(c.Request.Context(), inboxID, userID); svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to remove member"}) return } c.JSON(http.StatusOK, gin.H{"inbox_id": inboxID, "user_id": userID, "removed": true}) } // UpdateMember updates a member's role or availability status. // PATCH /api/v1/accounts/:id/inboxes/:inbox_id/members/:user_id // Reference: Chatwoot inbox_members#update func (h *InboxMemberHandler) UpdateMember(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } userID, err := parseUintParam(c, "user_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user id"}) return } var req service.UpdateMemberRequest if bindErr := c.ShouldBindJSON(&req); bindErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) return } member, svcErr := h.svc.UpdateMember(c.Request.Context(), inboxID, userID, req) if svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to update member"}) return } c.JSON(http.StatusOK, member) } // UpdateMultiple replaces all members of an inbox with a new set of user IDs (batch seat assignment). // PATCH /api/v1/accounts/:id/inboxes/:inbox_id/members/update_multiple // Reference: Chatwoot inbox_members#update (batch replace) func (h *InboxMemberHandler) UpdateMultiple(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } var req service.UpdateMultipleRequest if bindErr := c.ShouldBindJSON(&req); bindErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) return } req.InboxID = inboxID members, svcErr := h.svc.UpdateMultiple(c.Request.Context(), req) if svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to update members"}) return } c.JSON(http.StatusOK, gin.H{ "members": members, "meta": gin.H{"count": len(members)}, }) } // ShowAccountScoped retrieves all agents assigned to an inbox using Chatwoot's account-level route. // GET /api/v1/accounts/:account_id/inbox_members/:inbox_id func (h *InboxMemberHandler) ShowAccountScoped(c *gin.Context) { inboxID, err := parseUintParam(c, "inbox_id") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) return } members, svcErr := h.svc.ListByInbox(c.Request.Context(), inboxID) if svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to list inbox members"}) return } c.JSON(http.StatusOK, gin.H{"members": members, "meta": gin.H{"count": len(members)}}) } // CreateAccountScoped adds or replaces inbox members using Chatwoot's account-level create route. // POST /api/v1/accounts/:account_id/inbox_members func (h *InboxMemberHandler) CreateAccountScoped(c *gin.Context) { h.updateAccountScoped(c) } // UpdateAccountScoped replaces all members of an inbox using Chatwoot's account-level update route. // PATCH /api/v1/accounts/:account_id/inbox_members func (h *InboxMemberHandler) UpdateAccountScoped(c *gin.Context) { h.updateAccountScoped(c) } func (h *InboxMemberHandler) updateAccountScoped(c *gin.Context) { var req service.UpdateMultipleRequest if bindErr := c.ShouldBindJSON(&req); bindErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) return } members, svcErr := h.svc.UpdateMultiple(c.Request.Context(), req) if svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to update members"}) return } c.JSON(http.StatusOK, gin.H{"members": members, "meta": gin.H{"count": len(members)}}) } // DestroyAccountScoped removes selected users from an inbox using Chatwoot's account-level route. // DELETE /api/v1/accounts/:account_id/inbox_members func (h *InboxMemberHandler) DestroyAccountScoped(c *gin.Context) { var req struct { InboxID uint `json:"inbox_id" binding:"required"` UserIDs []uint `json:"user_ids" binding:"required"` } if bindErr := c.ShouldBindJSON(&req); bindErr != nil { c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) return } for _, userID := range req.UserIDs { if svcErr := h.svc.RemoveMember(c.Request.Context(), req.InboxID, userID); svcErr != nil { c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to remove member"}) return } } c.Status(http.StatusOK) }