149 lines
5.9 KiB
Go
149 lines
5.9 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
)
|
|
|
|
// ConversationParticipantService implements business logic for ConversationParticipant operations.
|
|
// Reference: Chatwoot app/controllers/api/v1/accounts/conversations/participants_controller.rb
|
|
// Chatwoot validates inbox access via assignable_agents before allowing participants.
|
|
type ConversationParticipantService struct {
|
|
repo *repository.ConversationParticipantRepo
|
|
conversationRepo *repository.ConversationRepo
|
|
assignableAgentSvc *AssignableAgentService
|
|
}
|
|
|
|
// NewConversationParticipantService creates a new ConversationParticipant service.
|
|
func NewConversationParticipantService(repo *repository.ConversationParticipantRepo, conversationRepo *repository.ConversationRepo) *ConversationParticipantService {
|
|
return &ConversationParticipantService{repo: repo, conversationRepo: conversationRepo}
|
|
}
|
|
|
|
// SetAssignableAgentService injects the assignable agent service for inbox access validation.
|
|
func (s *ConversationParticipantService) SetAssignableAgentService(svc *AssignableAgentService) {
|
|
s.assignableAgentSvc = svc
|
|
}
|
|
|
|
// List retrieves all participants for a conversation.
|
|
// Chatwoot: @participants = @conversation.conversation_participants
|
|
func (s *ConversationParticipantService) List(ctx context.Context, accountID, conversationID uint) ([]model.ConversationParticipant, error) {
|
|
if _, err := s.conversationRepo.FindByAccountAndID(ctx, accountID, conversationID); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.repo.FindByConversationID(ctx, conversationID)
|
|
}
|
|
|
|
// Add adds a participant to a conversation.
|
|
// Chatwoot: find_or_create_by(user_id), validates inbox access via assignable_agents
|
|
func (s *ConversationParticipantService) Add(ctx context.Context, accountID, conversationID, userID uint, role string) (*model.ConversationParticipant, error) {
|
|
conversation, err := s.conversationRepo.FindByAccountAndID(ctx, accountID, conversationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Chatwoot: validates :ensure_inbox_access — user must be in assignable_agents
|
|
if s.assignableAgentSvc != nil {
|
|
if err := s.validateInboxAccess(ctx, accountID, conversation.InboxID, userID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Chatwoot: before_validation :ensure_account_id — auto-sets account_id from conversation
|
|
// find_or_create_by pattern
|
|
existing, err := s.repo.FindByConversationAndUserID(ctx, conversationID, userID)
|
|
if err == nil && existing != nil {
|
|
return existing, nil
|
|
}
|
|
|
|
participant := &model.ConversationParticipant{
|
|
AccountID: accountID,
|
|
ConversationID: conversationID,
|
|
UserID: userID,
|
|
Role: role,
|
|
}
|
|
if err := s.repo.Create(ctx, participant); err != nil {
|
|
applogger.L().Errorf("ConversationParticipantService.Add: %v", err)
|
|
return nil, err
|
|
}
|
|
return participant, nil
|
|
}
|
|
|
|
// Update updates a participant's role in a conversation.
|
|
func (s *ConversationParticipantService) Update(ctx context.Context, accountID, conversationID, userID uint, role string) (*model.ConversationParticipant, error) {
|
|
if _, err := s.conversationRepo.FindByAccountAndID(ctx, accountID, conversationID); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.repo.UpdateRole(ctx, conversationID, userID, role); err != nil {
|
|
applogger.L().Errorf("ConversationParticipantService.Update: %v", err)
|
|
return nil, err
|
|
}
|
|
return s.repo.FindByConversationAndUserID(ctx, conversationID, userID)
|
|
}
|
|
|
|
// Remove removes a participant from a conversation.
|
|
// Chatwoot: conversation_participants.find_by(user_id: user_id)&.destroy
|
|
func (s *ConversationParticipantService) Remove(ctx context.Context, accountID, conversationID, userID uint) error {
|
|
if _, err := s.conversationRepo.FindByAccountAndID(ctx, accountID, conversationID); err != nil {
|
|
return err
|
|
}
|
|
return s.repo.DeleteByConversationAndUserID(ctx, conversationID, userID)
|
|
}
|
|
|
|
// BatchUpdate adds and removes participants in bulk.
|
|
// Chatwoot: update action — adds via find_or_create_by, removes via destroy
|
|
func (s *ConversationParticipantService) BatchUpdate(ctx context.Context, accountID, conversationID uint, addUserIDs []uint, removeUserIDs []uint, role string) ([]model.ConversationParticipant, error) {
|
|
conversation, err := s.conversationRepo.FindByAccountAndID(ctx, accountID, conversationID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Add new participants (find_or_create_by pattern)
|
|
for _, userID := range addUserIDs {
|
|
if s.assignableAgentSvc != nil {
|
|
if err := s.validateInboxAccess(ctx, accountID, conversation.InboxID, userID); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
existing, _ := s.repo.FindByConversationAndUserID(ctx, conversationID, userID)
|
|
if existing != nil {
|
|
continue
|
|
}
|
|
participant := &model.ConversationParticipant{
|
|
AccountID: accountID,
|
|
ConversationID: conversationID,
|
|
UserID: userID,
|
|
Role: role,
|
|
}
|
|
if err := s.repo.Create(ctx, participant); err != nil {
|
|
applogger.L().Errorf("BatchUpdate add: %v", err)
|
|
}
|
|
}
|
|
|
|
// Remove old participants
|
|
for _, userID := range removeUserIDs {
|
|
if err := s.repo.DeleteByConversationAndUserID(ctx, conversationID, userID); err != nil {
|
|
applogger.L().Errorf("BatchUpdate remove user %d: %v", userID, err)
|
|
}
|
|
}
|
|
|
|
return s.repo.FindByConversationID(ctx, conversationID)
|
|
}
|
|
|
|
// validateInboxAccess checks that a user has access to the conversation's inbox.
|
|
// Chatwoot: errors.add(:user, 'must have inbox access') if conversation.inbox.assignable_agents.exclude?(user)
|
|
func (s *ConversationParticipantService) validateInboxAccess(ctx context.Context, accountID, inboxID, userID uint) error {
|
|
agents, err := s.assignableAgentSvc.FindAssignableAgents(ctx, accountID, []uint{inboxID})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to verify inbox access: %w", err)
|
|
}
|
|
for _, agent := range agents {
|
|
if agent.ID == userID {
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("user %d must have inbox access", userID)
|
|
} |