feat(conversations): queue bulk actions
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
"github.com/gochat/gochat/internal/worker"
|
||||
"github.com/gochat/gochat/pkg/response"
|
||||
)
|
||||
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
type BulkActionHandler struct {
|
||||
conversationSvc *service.ConversationService
|
||||
contactSvc *service.ContactService
|
||||
worker *worker.WorkerPool
|
||||
}
|
||||
|
||||
// NewBulkActionHandler creates a new BulkActionHandler.
|
||||
@@ -22,16 +24,22 @@ func NewBulkActionHandler(conversationSvc *service.ConversationService, contactS
|
||||
return &BulkActionHandler{conversationSvc: conversationSvc, contactSvc: contactSvc}
|
||||
}
|
||||
|
||||
func (h *BulkActionHandler) WithWorkerPool(wp *worker.WorkerPool) *BulkActionHandler {
|
||||
h.worker = wp
|
||||
return h
|
||||
}
|
||||
|
||||
// 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
|
||||
Type string `json:"type" binding:"required"` // "Conversation" or "Contact"
|
||||
ActionName string `json:"action_name,omitempty"` // legacy local action names
|
||||
IDs []uint `json:"ids" binding:"required,min=1"` // Chatwoot sends conversation display IDs
|
||||
Fields service.ConversationBulkActionFields `json:"fields,omitempty"`
|
||||
Labels service.ConversationBulkActionLabels `json:"labels,omitempty"`
|
||||
AssigneeID *uint `json:"assignee_id,omitempty"` // legacy local assign field
|
||||
TeamID *uint `json:"team_id,omitempty"` // legacy local team field
|
||||
SnoozedUntil string `json:"snoozed_until,omitempty"` // for conversation snooze
|
||||
}
|
||||
|
||||
// Create processes a bulk action request.
|
||||
@@ -62,6 +70,29 @@ func (h *BulkActionHandler) Create(c *gin.Context) {
|
||||
|
||||
// handleConversationBulk processes bulk actions on conversations.
|
||||
func (h *BulkActionHandler) handleConversationBulk(c *gin.Context, accountID uint, req BulkActionRequest) {
|
||||
if h.worker != nil {
|
||||
params := service.ConversationBulkActionParams{
|
||||
Type: req.Type,
|
||||
ActionName: req.ActionName,
|
||||
IDs: req.IDs,
|
||||
Fields: req.Fields,
|
||||
Labels: req.Labels,
|
||||
SnoozedUntil: req.SnoozedUntil,
|
||||
}
|
||||
if req.AssigneeID != nil && params.Fields.AssigneeID == nil {
|
||||
params.Fields.AssigneeID = req.AssigneeID
|
||||
}
|
||||
if req.TeamID != nil && params.Fields.TeamID == nil {
|
||||
params.Fields.TeamID = req.TeamID
|
||||
}
|
||||
if _, err := service.EnqueueConversationBulkAction(c.Request.Context(), h.worker, accountID, getUserID(c), params); err != nil {
|
||||
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to enqueue bulk action")
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
successCount := 0
|
||||
failCount := 0
|
||||
|
||||
@@ -86,8 +117,8 @@ func (h *BulkActionHandler) handleConversationBulk(c *gin.Context, accountID uin
|
||||
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)
|
||||
if len(req.Labels.Add) > 0 {
|
||||
_, svcErr = h.conversationSvc.UpdateLabels(c.Request.Context(), accountID, convID, req.Labels.Add)
|
||||
}
|
||||
default:
|
||||
failCount++
|
||||
@@ -137,4 +168,4 @@ func (h *BulkActionHandler) handleContactBulk(c *gin.Context, accountID uint, re
|
||||
"success_count": successCount,
|
||||
"fail_count": failCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
"github.com/gochat/gochat/internal/service"
|
||||
"github.com/gochat/gochat/internal/worker"
|
||||
)
|
||||
|
||||
func TestBulkActionHandler_ConversationEnqueuesChatwootPayload(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db, err := gorm.Open(sqlite.Open("file:bulk-action-handler?mode=memory&cache=private"), &gorm.Config{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, db.AutoMigrate(&model.BackgroundJob{}))
|
||||
sqlDB, _ := db.DB()
|
||||
t.Cleanup(func() { _ = sqlDB.Close() })
|
||||
|
||||
wp := worker.NewWorkerPool(db)
|
||||
handler := NewBulkActionHandler(nil, nil).WithWorkerPool(wp)
|
||||
router := gin.New()
|
||||
router.POST("/api/v1/accounts/:account_id/bulk_actions", handler.Create)
|
||||
|
||||
body := []byte(`{"type":"Conversation","ids":[101,102],"fields":{"status":"resolved"},"labels":{"add":["vip"],"remove":["old"]}}`)
|
||||
request := httptest.NewRequest(http.MethodPost, "/api/v1/accounts/7/bulk_actions", bytes.NewReader(body))
|
||||
request.Header.Set("Content-Type", "application/json")
|
||||
request.Header.Set("X-User-ID", "42")
|
||||
response := httptest.NewRecorder()
|
||||
router.ServeHTTP(response, request)
|
||||
|
||||
require.Equal(t, http.StatusOK, response.Code)
|
||||
require.Empty(t, response.Body.String())
|
||||
|
||||
var job model.BackgroundJob
|
||||
require.NoError(t, db.Where("job_type = ? AND queue = ?", service.TaskTypeConversationBulkAction, "medium").First(&job).Error)
|
||||
require.Contains(t, string(job.Payload), `"account_id":7`)
|
||||
require.Contains(t, string(job.Payload), `"user_id":42`)
|
||||
require.Contains(t, string(job.Payload), `"ids":[101,102]`)
|
||||
require.Contains(t, string(job.Payload), `"status":"resolved"`)
|
||||
require.Contains(t, string(job.Payload), `"add":["vip"]`)
|
||||
}
|
||||
Reference in New Issue
Block a user