package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/language" "RenewCMS/internal/domain/localization" "RenewCMS/internal/domain/site" "RenewCMS/internal/domain/transaction" "context" "errors" "fmt" "slices" ) type ApplyLanguageSetupUseCase struct { transactor transaction.Transactor recorder audit.Recorder } func NewApplyLanguageSetupUseCase(transactor transaction.Transactor, recorder audit.Recorder) *ApplyLanguageSetupUseCase { return &ApplyLanguageSetupUseCase{transactor, recorder} } func (a *ApplyLanguageSetupUseCase) Apply(ctx context.Context, choices []localization.SetupChoice) (localization.SetupResult, error) { event := audit.NewEvent(audit.ActionSiteLanguageSetup, audit.CategorySites) result, confirmed, err := a.confirm(ctx, choices) if errors.Is(err, transaction.ErrLockContention) { err = fmt.Errorf("%w: %w", localization.ErrSetupInProgress, err) } if err != nil { recordFailure(ctx, a.recorder, event, err) return localization.SetupResult{}, err } for _, found := range confirmed { a.recordConfirmed(ctx, found, result) } return result, nil } func (a *ApplyLanguageSetupUseCase) confirm(ctx context.Context, choices []localization.SetupChoice) (localization.SetupResult, []site.Site, error) { chosen, err := normalizeSetupChoices(choices) if err != nil { return localization.SetupResult{}, nil, err } var result localization.SetupResult var confirmed []site.Site err = a.transactor.Run(ctx, func(tx transaction.Repositories) error { result = localization.SetupResult{Sites: []localization.SetupAssignment{}, Skipped: []string{}} confirmed = nil for key := range chosen { if _, err := tx.Sites.GetByKey(key); err != nil { return err } } pending, err := lockPendingSites(tx) if err != nil { return err } for _, found := range pending { if _, ok := chosen[found.Key]; !ok { return localization.ErrSetupIncomplete } } for _, found := range pending { assignment, err := confirmSite(tx, found, chosen[found.Key]) if err != nil { return err } result.Assigned += assignment.Assigned result.Sites = append(result.Sites, assignment) confirmed = append(confirmed, found) } result.Skipped = skippedChoices(chosen, pending) return nil }) if err != nil { return localization.SetupResult{}, nil, err } return result, confirmed, nil } func (a *ApplyLanguageSetupUseCase) recordConfirmed(ctx context.Context, found site.Site, result localization.SetupResult) { event := audit.NewEvent(audit.ActionSiteLanguageSetup, audit.CategorySites) event.Target = siteTarget(found) for _, assignment := range result.Sites { if assignment.Key == found.Key { event.Changes = []audit.Change{audit.TextChange(audit.FieldDefaultLanguage, "", assignment.Lang)} event.Params[audit.ParamAssigned] = assignment.Assigned } } a.recorder.Record(ctx, event) } func lockPendingSites(tx transaction.Repositories) ([]site.Site, error) { locked := map[uint32]bool{} for { pending, err := pendingSites(tx.Sites, tx.Localization) if err != nil { return nil, err } missing := []uint32{} for _, id := range siteIDs(pending) { if !locked[id] { missing = append(missing, id) } } if len(missing) == 0 { return pending, nil } if err := tx.Sites.TryLockSites(missing); err != nil { return nil, err } for _, id := range missing { locked[id] = true } } } func confirmSite(tx transaction.Repositories, found site.Site, tag string) (localization.SetupAssignment, error) { if err := tx.Localization.ConfirmSetup(found.ID, tag); err != nil { return localization.SetupAssignment{}, err } assigned, err := tx.Articles.AssignUnassignedLanguage(found.ID, tag) if err != nil { return localization.SetupAssignment{}, err } return localization.SetupAssignment{Key: found.Key, Lang: tag, Assigned: assigned}, nil } func skippedChoices(chosen map[string]string, pending []site.Site) []string { skipped := []string{} for key := range chosen { if !slices.ContainsFunc(pending, func(found site.Site) bool { return found.Key == key }) { skipped = append(skipped, key) } } slices.Sort(skipped) return skipped } func normalizeSetupChoices(choices []localization.SetupChoice) (map[string]string, error) { chosen := make(map[string]string, len(choices)) for _, choice := range choices { tag, err := language.Normalize(choice.Lang) if err != nil { return nil, err } if _, repeated := chosen[choice.Key]; repeated { return nil, localization.ErrDuplicateSite } chosen[choice.Key] = tag } return chosen, nil } func siteIDs(sites []site.Site) []uint32 { ids := make([]uint32, 0, len(sites)) for _, found := range sites { ids = append(ids, found.ID) } return ids }