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" ) // BulkActionHandler handles generic bulk actions for Conversations and Contacts. // Reference: Chatwoot app/controllers/api/v1/accounts/bulk_actions_controller.rb type BulkActionHandler struct { conversationSvc *service.ConversationService contactSvc *service.ContactService } // NewBulkActionHandler creates a new BulkActionHandler. func NewBulkActionHandler(conversationSvc *service.ConversationService, contactSvc *service.ContactService) *BulkActionHandler { return &BulkActionHandler{conversationSvc: conversationSvc, contactSvc: contactSvc} } // BulkActionRequest is the DTO for generic bulk actions. // Reference: Chatwoot bulk_actions_controller#create — params[:type], params[:action_name], params[:ids] type BulkActionRequest struct { Type string `json:"type" binding:"required"` // "Conversation" or "Contact" ActionName string `json:"action_name" binding:"required"` // "resolve", "open", "snooze", "delete", "label_add", "label_remove" IDs []uint `json:"ids" binding:"required,min=1"` // target object IDs AssigneeID *uint `json:"assignee_id,omitempty"` // for conversation assign TeamID *uint `json:"team_id,omitempty"` // for conversation team assign SnoozedUntil string `json:"snoozed_until,omitempty"` // for conversation snooze Labels []string `json:"labels,omitempty"` // labels to add/update } // Create processes a bulk action request. // POST /api/v1/accounts/:account_id/bulk_actions // Reference: Chatwoot bulk_actions_controller#create — returns 200 OK on success func (h *BulkActionHandler) Create(c *gin.Context) { accountID, err := parseUintParam(c, "account_id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id") return } var req BulkActionRequest if bindErr := c.ShouldBindJSON(&req); bindErr != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, bindErr.Error()) return } switch req.Type { case "Conversation": h.handleConversationBulk(c, accountID, req) case "Contact": h.handleContactBulk(c, accountID, req) default: c.JSON(http.StatusUnprocessableEntity, gin.H{"success": false}) } } // handleConversationBulk processes bulk actions on conversations. func (h *BulkActionHandler) handleConversationBulk(c *gin.Context, accountID uint, req BulkActionRequest) { successCount := 0 failCount := 0 for _, convID := range req.IDs { var svcErr error switch req.ActionName { case "resolve": _, svcErr = h.conversationSvc.ToggleStatus(c.Request.Context(), accountID, convID, service.ToggleStatusRequest{Status: string(model.ConversationStatusResolved)}) case "open": _, svcErr = h.conversationSvc.ToggleStatus(c.Request.Context(), accountID, convID, service.ToggleStatusRequest{Status: string(model.ConversationStatusOpen)}) case "snooze": _, svcErr = h.conversationSvc.ToggleStatus(c.Request.Context(), accountID, convID, service.ToggleStatusRequest{Status: string(model.ConversationStatusSnoozed)}) case "assign": if req.AssigneeID != nil { _, svcErr = h.conversationSvc.AssignAgent(c.Request.Context(), accountID, convID, *req.AssigneeID) } case "unassign": _, svcErr = h.conversationSvc.UnassignAgent(c.Request.Context(), accountID, convID) case "assign_team": _, svcErr = h.conversationSvc.AssignTeam(c.Request.Context(), accountID, convID, req.AssigneeID, req.TeamID) case "delete": svcErr = h.conversationSvc.Delete(c.Request.Context(), accountID, convID) case "label_add": if len(req.Labels) > 0 { _, svcErr = h.conversationSvc.UpdateLabels(c.Request.Context(), accountID, convID, req.Labels) } default: failCount++ continue } if svcErr != nil { failCount++ } else { successCount++ } } response.OK(c, gin.H{ "success_count": successCount, "fail_count": failCount, }) } // handleContactBulk processes bulk actions on contacts. // Reference: Chatwoot only supports "delete" and label operations for contacts in bulk_actions. func (h *BulkActionHandler) handleContactBulk(c *gin.Context, accountID uint, req BulkActionRequest) { successCount := 0 failCount := 0 for _, contactID := range req.IDs { var svcErr error switch req.ActionName { case "delete": svcErr = h.contactSvc.Delete(c.Request.Context(), accountID, contactID) default: // Chatwoot: other contact actions (labels) require separate async jobs // For now, return error for unsupported actions failCount++ continue } if svcErr != nil { failCount++ } else { successCount++ } } response.OK(c, gin.H{ "success_count": successCount, "fail_count": failCount, }) }