Files
gochat/internal/service/label_service.go
T
2026-06-04 15:44:48 +08:00

152 lines
5.2 KiB
Go

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
}