package service import ( "context" "encoding/json" "fmt" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" ) // CaptainScenarioService implements business logic for CaptainScenario operations. // Reference: Chatwoot enterprise/app/controllers/api/v1/captain/scenarios_controller.rb type CaptainScenarioService struct { scenarioRepo *repository.CaptainScenarioRepo assistantRepo *repository.CaptainAssistantRepo } // NewCaptainScenarioService creates a new CaptainScenarioService. func NewCaptainScenarioService(scenarioRepo *repository.CaptainScenarioRepo, assistantRepo ...*repository.CaptainAssistantRepo) *CaptainScenarioService { s := &CaptainScenarioService{scenarioRepo: scenarioRepo} if len(assistantRepo) > 0 { s.assistantRepo = assistantRepo[0] } return s } // --- Request DTOs --- // CreateScenarioRequest is the DTO for creating a scenario. type CreateScenarioRequest struct { Title string `json:"title" validate:"required"` Description string `json:"description"` Instruction string `json:"instruction"` Enabled *bool `json:"enabled"` Tools json.RawMessage `json:"tools"` } // UpdateScenarioRequest is the DTO for updating a scenario. type UpdateScenarioRequest struct { Title string `json:"title"` Description string `json:"description"` Instruction string `json:"instruction"` Enabled *bool `json:"enabled"` Tools json.RawMessage `json:"tools"` } // --- Service methods --- // Create creates a new scenario for an assistant. func (s *CaptainScenarioService) Create(ctx context.Context, accountID, assistantID uint, req *CreateScenarioRequest) (*model.CaptainScenario, error) { if s.assistantRepo != nil { if _, err := s.assistantRepo.GetByAccountAndID(ctx, accountID, assistantID); err != nil { return nil, fmt.Errorf("assistant not found: %w", err) } } enabled := true if req.Enabled != nil { enabled = *req.Enabled } scenario := &model.CaptainScenario{ AccountID: accountID, AssistantID: assistantID, Title: req.Title, Description: req.Description, Instruction: req.Instruction, Enabled: enabled, Tools: req.Tools, } if err := s.scenarioRepo.Create(ctx, scenario); err != nil { applogger.L().Errorf("Create captain scenario: %v", err) return nil, fmt.Errorf("create scenario: %w", err) } if created, err := s.scenarioRepo.GetByAccountAssistantAndID(ctx, accountID, assistantID, scenario.ID); err == nil { return created, nil } return scenario, nil } func (s *CaptainScenarioService) Get(ctx context.Context, accountID, assistantID, id uint) (*model.CaptainScenario, error) { scenario, err := s.scenarioRepo.GetByAccountAssistantAndID(ctx, accountID, assistantID, id) if err != nil { applogger.L().Errorf("Get captain scenario: %v", err) return nil, fmt.Errorf("get scenario: %w", err) } return scenario, nil } // GetByID retrieves a scenario by ID. func (s *CaptainScenarioService) GetByID(ctx context.Context, id uint) (*model.CaptainScenario, error) { scenario, err := s.scenarioRepo.GetByID(ctx, id) if err != nil { applogger.L().Errorf("Get captain scenario: %v", err) return nil, fmt.Errorf("get scenario: %w", err) } return scenario, nil } // Update updates an existing scenario. func (s *CaptainScenarioService) Update(ctx context.Context, id uint, req *UpdateScenarioRequest) (*model.CaptainScenario, error) { scenario, err := s.scenarioRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("scenario not found: %w", err) } if req.Title != "" { scenario.Title = req.Title } if req.Description != "" { scenario.Description = req.Description } if req.Instruction != "" { scenario.Instruction = req.Instruction } if req.Enabled != nil { scenario.Enabled = *req.Enabled } if len(req.Tools) > 0 && string(req.Tools) != "null" { scenario.Tools = req.Tools } if err := s.scenarioRepo.Update(ctx, scenario); err != nil { applogger.L().Errorf("Update captain scenario: %v", err) return nil, fmt.Errorf("update scenario: %w", err) } return scenario, nil } func (s *CaptainScenarioService) UpdateScoped(ctx context.Context, accountID, assistantID, id uint, req *UpdateScenarioRequest) (*model.CaptainScenario, error) { scenario, err := s.scenarioRepo.GetByAccountAssistantAndID(ctx, accountID, assistantID, id) if err != nil { return nil, fmt.Errorf("scenario not found: %w", err) } if req.Title != "" { scenario.Title = req.Title } if req.Description != "" { scenario.Description = req.Description } if req.Instruction != "" { scenario.Instruction = req.Instruction } if req.Enabled != nil { scenario.Enabled = *req.Enabled } if len(req.Tools) > 0 && string(req.Tools) != "null" { scenario.Tools = req.Tools } if err := s.scenarioRepo.Update(ctx, scenario); err != nil { applogger.L().Errorf("Update captain scenario: %v", err) return nil, fmt.Errorf("update scenario: %w", err) } return scenario, nil } // Delete deletes a scenario by ID. func (s *CaptainScenarioService) Delete(ctx context.Context, id uint) error { if err := s.scenarioRepo.Delete(ctx, id); err != nil { applogger.L().Errorf("Delete captain scenario: %v", err) return fmt.Errorf("delete scenario: %w", err) } return nil } func (s *CaptainScenarioService) DeleteScoped(ctx context.Context, accountID, assistantID, id uint) error { if _, err := s.scenarioRepo.GetByAccountAssistantAndID(ctx, accountID, assistantID, id); err != nil { return fmt.Errorf("scenario not found: %w", err) } if err := s.scenarioRepo.DeleteByAccountAssistant(ctx, accountID, assistantID, id); err != nil { applogger.L().Errorf("Delete captain scenario: %v", err) return fmt.Errorf("delete scenario: %w", err) } return nil } // ListByAssistant retrieves scenarios for an assistant with pagination. func (s *CaptainScenarioService) ListByAssistant(ctx context.Context, assistantID uint, offset, limit int) ([]model.CaptainScenario, int64, error) { scenarios, count, err := s.scenarioRepo.ListByAssistant(ctx, assistantID, offset, limit) if err != nil { applogger.L().Errorf("List captain scenarios: %v", err) return nil, 0, fmt.Errorf("list scenarios: %w", err) } return scenarios, count, nil } func (s *CaptainScenarioService) ListByAccountAssistant(ctx context.Context, accountID, assistantID uint) ([]model.CaptainScenario, int64, error) { if s.assistantRepo != nil { if _, err := s.assistantRepo.GetByAccountAndID(ctx, accountID, assistantID); err != nil { return nil, 0, fmt.Errorf("assistant not found: %w", err) } } scenarios, count, err := s.scenarioRepo.ListEnabledByAccountAssistant(ctx, accountID, assistantID) if err != nil { applogger.L().Errorf("List captain scenarios: %v", err) return nil, 0, fmt.Errorf("list scenarios: %w", err) } return scenarios, count, nil }