package v1 import ( "net/http" "github.com/gin-gonic/gin" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/service" "github.com/gochat/gochat/pkg/response" ) // ConversationParticipantHandler handles conversation participant API endpoints. // Reference: Chatwoot app/controllers/api/v1/conversations/participants_controller.rb type ConversationParticipantHandler struct { participantSvc *service.ConversationParticipantService } // NewConversationParticipantHandler creates a new ConversationParticipantHandler. func NewConversationParticipantHandler(participantSvc *service.ConversationParticipantService) *ConversationParticipantHandler { return &ConversationParticipantHandler{participantSvc: participantSvc} } // List retrieves all participants for a conversation. // GET /api/v1/accounts/:account_id/conversations/:conversation_id/participants func (h *ConversationParticipantHandler) List(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } participants, svcErr := h.participantSvc.List(c.Request.Context(), accountID, conversationID) if svcErr != nil { handleServiceError(c, svcErr) return } c.JSON(http.StatusOK, serializeConversationParticipantAgents(accountID, participants)) } // Add adds a participant to a conversation. // POST /api/v1/accounts/:account_id/conversations/:conversation_id/participants func (h *ConversationParticipantHandler) Add(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } var req struct { UserID uint `json:"user_id"` UserIDs []uint `json:"user_ids"` Role string `json:"role"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } userIDs := participantRequestUserIDs(req.UserID, req.UserIDs) if len(userIDs) == 0 { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, "user_ids is required") return } participants, svcErr := h.participantSvc.AddMany(c.Request.Context(), accountID, conversationID, userIDs, req.Role) if svcErr != nil { handleServiceError(c, svcErr) return } c.JSON(http.StatusOK, serializeConversationParticipantAgents(accountID, participants)) } // Update updates a participant's role in a conversation. // PATCH /api/v1/accounts/:account_id/conversations/:conversation_id/participants/:user_id func (h *ConversationParticipantHandler) Update(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } userID, err := parseUintParam(c, "user_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user_id") return } var req struct { Role string `json:"role" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } participant, svcErr := h.participantSvc.Update(c.Request.Context(), accountID, conversationID, userID, req.Role) if svcErr != nil { handleServiceError(c, svcErr) return } c.JSON(http.StatusOK, serializeConversationParticipantAgents(accountID, []model.ConversationParticipant{*participant})) } // Remove removes a participant from a conversation. // DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/participants/:user_id func (h *ConversationParticipantHandler) Remove(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } userID, err := parseUintParam(c, "user_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid user_id") return } if svcErr := h.participantSvc.Remove(c.Request.Context(), accountID, conversationID, userID); svcErr != nil { handleServiceError(c, svcErr) return } c.Status(http.StatusOK) } // Destroy removes multiple participants from a conversation. // DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/participants func (h *ConversationParticipantHandler) Destroy(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } var req struct { UserIDs []uint `json:"user_ids"` } if c.Request.ContentLength != 0 { if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } } if svcErr := h.participantSvc.RemoveMany(c.Request.Context(), accountID, conversationID, req.UserIDs); svcErr != nil { handleServiceError(c, svcErr) return } c.Status(http.StatusOK) } // BatchUpdate adds and/or removes multiple participants in a single call. // PATCH /api/v1/accounts/:account_id/conversations/:conversation_id/participants // Accepts: { "user_ids": [1,2,3] } as the final participant set. // Reference: Chatwoot app/controllers/api/v1/conversations/participants_controller.rb#update func (h *ConversationParticipantHandler) BatchUpdate(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } conversationID, err := parseUintParam(c, "conversation_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid conversation_id") return } var req struct { UserIDs []uint `json:"user_ids"` Role string `json:"role"` } if err := c.ShouldBindJSON(&req); err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error()) return } participants, svcErr := h.participantSvc.Replace(c.Request.Context(), accountID, conversationID, req.UserIDs, req.Role) if svcErr != nil { handleServiceError(c, svcErr) return } c.JSON(http.StatusOK, serializeConversationParticipantAgents(accountID, participants)) } func participantRequestUserIDs(userID uint, userIDs []uint) []uint { if len(userIDs) > 0 { return userIDs } if userID != 0 { return []uint{userID} } return nil } func serializeConversationParticipantAgents(accountID uint, participants []model.ConversationParticipant) []map[string]any { payload := make([]map[string]any, 0, len(participants)) for i := range participants { payload = append(payload, serializeAgentUser(participants[i].User, accountID, "", "", false, 0)) } return payload }