package service import ( "context" "errors" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" ) // LabelService implements business logic for conversation-label associations // and batch label management. type LabelService struct { convLabelRepo *repository.ConversationLabelRepo tagRepo *repository.TagRepo } func NewLabelService(convLabelRepo *repository.ConversationLabelRepo, tagRepo *repository.TagRepo) *LabelService { return &LabelService{convLabelRepo: convLabelRepo, tagRepo: tagRepo} } // AddLabelToConversation attaches a label/tag to a conversation. func (s *LabelService) AddLabelToConversation(ctx context.Context, accountID, conversationID, tagID uint) (*model.ConversationLabel, error) { // Verify tag exists and belongs to the account tag, err := s.tagRepo.GetByID(ctx, tagID) if err != nil { return nil, errors.New("tag not found") } if tag.AccountID != accountID { return nil, errors.New("tag does not belong to this account") } // Check for duplicate exists, err := s.convLabelRepo.Exists(ctx, conversationID, tagID) if err != nil { return nil, err } if exists { return nil, errors.New("label already attached to this conversation") } cl := &model.ConversationLabel{ ConversationID: conversationID, TagID: tagID, AccountID: accountID, } if err := s.convLabelRepo.AddLabel(ctx, cl); err != nil { applogger.L().Errorf("Add label to conversation: %v", err) return nil, err } return cl, nil } // RemoveLabelFromConversation detaches a label/tag from a conversation. func (s *LabelService) RemoveLabelFromConversation(ctx context.Context, conversationID, tagID uint) error { if err := s.convLabelRepo.RemoveLabel(ctx, conversationID, tagID); err != nil { applogger.L().Errorf("Remove label from conversation: %v", err) return err } return nil } // GetConversationLabels returns all labels attached to a conversation. func (s *LabelService) GetConversationLabels(ctx context.Context, conversationID uint) ([]model.ConversationLabel, error) { labels, err := s.convLabelRepo.FindByConversationID(ctx, conversationID) if err != nil { applogger.L().Errorf("Get conversation labels: %v", err) return nil, err } return labels, nil } // ReplaceConversationLabels replaces all labels on a conversation with a new set. func (s *LabelService) ReplaceConversationLabels(ctx context.Context, accountID, conversationID uint, tagIDs []uint) ([]model.ConversationLabel, error) { // Validate all tagIDs belong to the account for _, tagID := range tagIDs { tag, err := s.tagRepo.GetByID(ctx, tagID) if err != nil { return nil, errors.New("tag not found: " + tag.Name) } if tag.AccountID != accountID { return nil, errors.New("tag does not belong to this account") } } if err := s.convLabelRepo.ReplaceLabels(ctx, conversationID, accountID, tagIDs); err != nil { applogger.L().Errorf("Replace conversation labels: %v", err) return nil, err } return s.convLabelRepo.FindByConversationID(ctx, conversationID) } // BatchAddLabelRequest is the DTO for batch adding a label to conversations. type BatchAddLabelRequest struct { TagID uint `json:"tag_id" validate:"required"` ConversationIDs []uint `json:"conversation_ids" validate:"required,min=1"` } // BatchRemoveLabelRequest is the DTO for batch removing a label from conversations. type BatchRemoveLabelRequest struct { TagID uint `json:"tag_id" validate:"required"` ConversationIDs []uint `json:"conversation_ids" validate:"required,min=1"` } // BatchAddLabel attaches a label to multiple conversations at once. func (s *LabelService) BatchAddLabel(ctx context.Context, accountID uint, req *BatchAddLabelRequest) error { // Verify tag belongs to account tag, err := s.tagRepo.GetByID(ctx, req.TagID) if err != nil { return errors.New("tag not found") } if tag.AccountID != accountID { return errors.New("tag does not belong to this account") } if err := s.convLabelRepo.BatchAddLabels(ctx, req.ConversationIDs, req.TagID, accountID); err != nil { applogger.L().Errorf("Batch add label: %v", err) return err } return nil } // BatchRemoveLabel detaches a label from multiple conversations at once. func (s *LabelService) BatchRemoveLabel(ctx context.Context, accountID uint, req *BatchRemoveLabelRequest) error { // Verify tag belongs to account tag, err := s.tagRepo.GetByID(ctx, req.TagID) if err != nil { return errors.New("tag not found") } if tag.AccountID != accountID { return errors.New("tag does not belong to this account") } if err := s.convLabelRepo.BatchRemoveLabels(ctx, req.ConversationIDs, req.TagID); err != nil { applogger.L().Errorf("Batch remove label: %v", err) return err } return nil } // GetConversationsByTag returns all conversations that have a specific tag. func (s *LabelService) GetConversationsByTag(ctx context.Context, accountID, tagID uint, page, perPage int) ([]model.ConversationLabel, int64, error) { offset := (page - 1) * perPage labels, count, err := s.convLabelRepo.FindByTagID(ctx, accountID, tagID, offset, perPage) if err != nil { applogger.L().Errorf("Get conversations by tag: %v", err) return nil, 0, err } return labels, count, nil }