package translation import ( "context" "fmt" "tercul/internal/app/authz" "tercul/internal/domain" platform_auth "tercul/internal/platform/auth" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/trace" ) // TranslationCommands contains the command handlers for the translation aggregate. type TranslationCommands struct { repo domain.TranslationRepository authzSvc *authz.Service tracer trace.Tracer } // NewTranslationCommands creates a new TranslationCommands handler. func NewTranslationCommands(repo domain.TranslationRepository, authzSvc *authz.Service) *TranslationCommands { return &TranslationCommands{ repo: repo, authzSvc: authzSvc, tracer: otel.Tracer("translation.commands"), } } // CreateOrUpdateTranslationInput represents the input for creating or updating a translation. type CreateOrUpdateTranslationInput struct { Title string Content string Description string Language string Status domain.TranslationStatus TranslatableID uint TranslatableType string TranslatorID *uint IsOriginalLanguage bool } // CreateOrUpdateTranslation creates a new translation or updates an existing one. func (c *TranslationCommands) CreateOrUpdateTranslation(ctx context.Context, input CreateOrUpdateTranslationInput) (*domain.Translation, error) { ctx, span := c.tracer.Start(ctx, "CreateOrUpdateTranslation") defer span.End() // Validate input first if input.Language == "" { return nil, fmt.Errorf("%w: language cannot be empty", domain.ErrValidation) } if input.TranslatableID == 0 { return nil, fmt.Errorf("%w: translatable ID cannot be zero", domain.ErrValidation) } if input.TranslatableType == "" { return nil, fmt.Errorf("%w: translatable type cannot be empty", domain.ErrValidation) } userID, ok := platform_auth.GetUserIDFromContext(ctx) if !ok { return nil, domain.ErrUnauthorized } // Authorize that the user can edit the parent entity. can, err := c.authzSvc.CanEditEntity(ctx, userID, input.TranslatableType, input.TranslatableID) if err != nil { return nil, fmt.Errorf("authorization check failed: %w", err) } if !can { return nil, domain.ErrForbidden } var translatorID uint if input.TranslatorID != nil { translatorID = *input.TranslatorID } else { translatorID = userID } translation := &domain.Translation{ Title: input.Title, Content: input.Content, Description: input.Description, Language: input.Language, Status: input.Status, TranslatableID: input.TranslatableID, TranslatableType: input.TranslatableType, TranslatorID: &translatorID, IsOriginalLanguage: input.IsOriginalLanguage, } if err := c.repo.Upsert(ctx, translation); err != nil { return nil, fmt.Errorf("failed to upsert translation: %w", err) } return translation, nil } // DeleteTranslation deletes a translation by ID. func (c *TranslationCommands) DeleteTranslation(ctx context.Context, id uint) error { ctx, span := c.tracer.Start(ctx, "DeleteTranslation") defer span.End() can, err := c.authzSvc.CanDeleteTranslation(ctx) if err != nil { return err } if !can { return domain.ErrForbidden } return c.repo.Delete(ctx, id) }