// Package service provides business logic / use cases for the application. // Services orchestrate domain operations using port interfaces. package service import ( "context" "errors" "github.com/google/uuid" "git.threesix.ai/jordan/foundary-1770670477/pkg/logging" "git.threesix.ai/jordan/foundary-1770670477/services/studio-api/internal/domain" "git.threesix.ai/jordan/foundary-1770670477/services/studio-api/internal/port" ) // ExampleService handles example-related business logic. type ExampleService struct { repo port.ExampleRepository logger *logging.Logger } // NewExampleService creates a new example service. func NewExampleService(repo port.ExampleRepository, logger *logging.Logger) *ExampleService { return &ExampleService{ repo: repo, logger: logger.WithService("ExampleService"), } } // List returns all examples. func (s *ExampleService) List(ctx context.Context) ([]domain.Example, error) { return s.repo.List(ctx) } // Get returns an example by ID. // Returns domain.ErrExampleNotFound if not found. func (s *ExampleService) Get(ctx context.Context, id domain.ExampleID) (*domain.Example, error) { return s.repo.Get(ctx, id) } // CreateInput contains the data needed to create an example. type CreateInput struct { Name string Description string } // Create creates a new example with duplicate detection. // Returns domain.ErrDuplicateExample if name already exists. // Returns domain.ErrInvalidExampleName if name is invalid. func (s *ExampleService) Create(ctx context.Context, input CreateInput) (*domain.Example, error) { // Check for duplicates exists, err := s.repo.ExistsByName(ctx, input.Name) if err != nil { return nil, err } if exists { return nil, domain.ErrDuplicateExample } // Generate new ID id := domain.ExampleID(uuid.New().String()) // Create domain entity (validates name) example, err := domain.NewExample(id, input.Name, input.Description) if err != nil { return nil, err } // Persist if err := s.repo.Create(ctx, example); err != nil { return nil, err } s.logger.Info("example created", "id", id, "name", input.Name) return example, nil } // UpdateInput contains the data needed to update an example. type UpdateInput struct { Name string Description string } // Update modifies an existing example. // Returns domain.ErrExampleNotFound if not found. // Returns domain.ErrDuplicateExample if new name conflicts with another example. // Returns domain.ErrInvalidExampleName if name is invalid. func (s *ExampleService) Update(ctx context.Context, id domain.ExampleID, input UpdateInput) (*domain.Example, error) { // Fetch existing example, err := s.repo.Get(ctx, id) if err != nil { return nil, err } // Check for name conflicts (only if name changed) if example.Name != input.Name { exists, err := s.repo.ExistsByName(ctx, input.Name) if err != nil { return nil, err } if exists { return nil, domain.ErrDuplicateExample } } // Update domain entity (validates name) if err := example.Update(input.Name, input.Description); err != nil { return nil, err } // Persist if err := s.repo.Update(ctx, example); err != nil { return nil, err } s.logger.Info("example updated", "id", id, "name", input.Name) return example, nil } // Delete removes an example by ID. // Returns domain.ErrExampleNotFound if not found. func (s *ExampleService) Delete(ctx context.Context, id domain.ExampleID) error { // Verify exists before delete if _, err := s.repo.Get(ctx, id); err != nil { if errors.Is(err, domain.ErrExampleNotFound) { return domain.ErrExampleNotFound } return err } if err := s.repo.Delete(ctx, id); err != nil { return err } s.logger.Info("example deleted", "id", id) return nil }