package service import ( "context" "fmt" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" pkgvalidator "github.com/gochat/gochat/pkg/validator" applogger "github.com/gochat/gochat/pkg/logger" ) // CaptainPreferenceService implements business logic for CaptainPreference operations. // Reference: Chatwoot enterprise/app/controllers/api/v1/captain/preferences_controller.rb type CaptainPreferenceService struct { repo *repository.CaptainPreferenceRepo } func NewCaptainPreferenceService(repo *repository.CaptainPreferenceRepo) *CaptainPreferenceService { return &CaptainPreferenceService{repo: repo} } // --- Request DTOs --- // CreatePreferenceRequest is the DTO for creating a preference. // Reference: Chatwoot enterprise/app/models/captain/preference.rb type CreatePreferenceRequest struct { Tone string `json:"tone" validate:"omitempty,oneof=professional casual friendly formal"` Language string `json:"language" validate:"omitempty,min=1,max=10"` ResponseGuidelines string `json:"response_guidelines" validate:"omitempty,max=2000"` AutoLabelEnabled bool `json:"auto_label_enabled"` AutoFollowUpEnabled bool `json:"auto_follow_up_enabled"` AutoReplyEnabled bool `json:"auto_reply_enabled"` MaxResponseLength int `json:"max_response_length" validate:"omitempty,min=50,max=5000"` CustomPromptSuffix string `json:"custom_prompt_suffix" validate:"omitempty,max=1000"` } // UpdatePreferenceRequest is the DTO for updating a preference. // Reference: Chatwoot enterprise/app/models/captain/preference.rb type UpdatePreferenceRequest struct { Tone string `json:"tone,omitempty" validate:"omitempty,oneof=professional casual friendly formal"` Language string `json:"language,omitempty" validate:"omitempty,min=1,max=10"` ResponseGuidelines string `json:"response_guidelines,omitempty" validate:"omitempty,max=2000"` AutoLabelEnabled *bool `json:"auto_label_enabled,omitempty"` AutoFollowUpEnabled *bool `json:"auto_follow_up_enabled,omitempty"` AutoReplyEnabled *bool `json:"auto_reply_enabled,omitempty"` MaxResponseLength *int `json:"max_response_length,omitempty" validate:"omitempty,min=50,max=5000"` CustomPromptSuffix string `json:"custom_prompt_suffix,omitempty" validate:"omitempty,max=1000"` } // --- Business logic --- // validTones are the allowed tone values for captain preferences. // Reference: Chatwoot enterprise/app/models/captain/preference.rb TONES var validTones = []string{"professional", "casual", "friendly", "formal"} // isValidTone checks if a tone value is valid. func isValidTone(tone string) bool { for _, t := range validTones { if t == tone { return true } } return false } // Create creates a new preference for an account. func (s *CaptainPreferenceService) Create(ctx context.Context, accountID uint, req *CreatePreferenceRequest) (*model.CaptainPreference, error) { // Validate request if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, fmt.Errorf("validation error: %w", err) } // Check if preference already exists for this account existing, err := s.repo.GetByAccountID(ctx, accountID) if err == nil && existing != nil { return nil, fmt.Errorf("preference already exists for account %d", accountID) } pref := &model.CaptainPreference{ AccountID: accountID, Tone: req.Tone, Language: req.Language, ResponseGuidelines: req.ResponseGuidelines, AutoLabelEnabled: req.AutoLabelEnabled, AutoFollowUpEnabled: req.AutoFollowUpEnabled, AutoReplyEnabled: req.AutoReplyEnabled, MaxResponseLength: req.MaxResponseLength, CustomPromptSuffix: req.CustomPromptSuffix, } // Apply defaults if pref.Tone == "" { pref.Tone = "professional" } if pref.Language == "" { pref.Language = "en" } if pref.MaxResponseLength == 0 { pref.MaxResponseLength = 500 } if err := s.repo.Create(ctx, pref); err != nil { applogger.L().Errorf("Create captain preference: %v", err) return nil, fmt.Errorf("create preference: %w", err) } return pref, nil } // Get retrieves the preference for an account. func (s *CaptainPreferenceService) Get(ctx context.Context, accountID uint) (*model.CaptainPreference, error) { pref, err := s.repo.GetByAccountID(ctx, accountID) if err != nil { return nil, fmt.Errorf("preference not found for account %d: %w", accountID, err) } return pref, nil } // Update updates the preference for an account. func (s *CaptainPreferenceService) Update(ctx context.Context, accountID uint, req *UpdatePreferenceRequest) (*model.CaptainPreference, error) { // Validate request if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, fmt.Errorf("validation error: %w", err) } pref, err := s.repo.GetByAccountID(ctx, accountID) if err != nil { return nil, fmt.Errorf("preference not found for account %d: %w", accountID, err) } // Apply partial updates if req.Tone != "" { pref.Tone = req.Tone } if req.Language != "" { pref.Language = req.Language } if req.ResponseGuidelines != "" { pref.ResponseGuidelines = req.ResponseGuidelines } if req.AutoLabelEnabled != nil { pref.AutoLabelEnabled = *req.AutoLabelEnabled } if req.AutoFollowUpEnabled != nil { pref.AutoFollowUpEnabled = *req.AutoFollowUpEnabled } if req.AutoReplyEnabled != nil { pref.AutoReplyEnabled = *req.AutoReplyEnabled } if req.MaxResponseLength != nil { pref.MaxResponseLength = *req.MaxResponseLength } if req.CustomPromptSuffix != "" { pref.CustomPromptSuffix = req.CustomPromptSuffix } if err := s.repo.Update(ctx, pref); err != nil { applogger.L().Errorf("Update captain preference: %v", err) return nil, fmt.Errorf("update preference: %w", err) } return pref, nil } // Delete removes the preference for an account. func (s *CaptainPreferenceService) Delete(ctx context.Context, accountID uint) error { if err := s.repo.Delete(ctx, accountID); err != nil { applogger.L().Errorf("Delete captain preference: %v", err) return fmt.Errorf("delete preference: %w", err) } return nil }