package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/localization" "RenewCMS/internal/domain/transaction" "context" "errors" "fmt" "slices" "strings" ) type UpdateSiteLanguagesUseCase struct { transactor transaction.Transactor recorder audit.Recorder } type SiteLanguageEntry struct { Lang string Enabled bool SocialLocale *string } type UpdateSiteLanguagesCommand struct { Key string Revision string Default string Languages []SiteLanguageEntry } func NewUpdateSiteLanguagesUseCase(transactor transaction.Transactor, recorder audit.Recorder) *UpdateSiteLanguagesUseCase { return &UpdateSiteLanguagesUseCase{transactor, recorder} } func (u *UpdateSiteLanguagesUseCase) Update(ctx context.Context, command UpdateSiteLanguagesCommand) (localization.View, error) { event := audit.NewEvent(audit.ActionSiteLanguagesEdit, audit.CategorySites) var view localization.View var before []localization.Row var after []localization.Row err := u.transactor.Run(ctx, func(tx transaction.Repositories) error { found, err := tx.Sites.GetByKey(command.Key) if err != nil { return err } event.Target = siteTarget(found) if err := tx.Sites.LockSite(found.ID); err != nil { return err } before, err = tx.Localization.Rows(found.ID) if err != nil { return err } if localization.NeedsSetup(before) { return localization.ErrSetupRequired } if localization.Revision(before) != command.Revision { return localization.ErrLanguagesChanged } counts, err := tx.Localization.VersionCounts(found.ID) if err != nil { return err } after, err = localization.Plan(before, counts, command.change()) if err != nil { return err } if !localization.SameRows(before, after) { if err := tx.Localization.Replace(found.ID, after); err != nil { return err } } view, err = readSiteLanguages(tx.Localization, found.ID) return err }) if errors.Is(err, transaction.ErrLockContention) { err = fmt.Errorf("%w: %w", localization.ErrLanguagesChanged, err) } if err != nil { recordFailure(ctx, u.recorder, event, err) return localization.View{}, err } if changes := languageChanges(before, after); len(changes) > 0 { event.Changes = changes u.recorder.Record(ctx, event) } return view, nil } func languageChanges(before []localization.Row, after []localization.Row) []audit.Change { changes := []audit.Change{} changes = audit.AppendTextChange(changes, audit.FieldDefaultLanguage, defaultTag(before), defaultTag(after)) return audit.AppendTextChange(changes, audit.FieldLanguages, languagesLabel(before), languagesLabel(after)) } func defaultTag(rows []localization.Row) string { for _, row := range rows { if row.IsDefault { return row.Tag } } return "" } func languagesLabel(rows []localization.Row) string { ordered := slices.Clone(rows) slices.SortFunc(ordered, func(left localization.Row, right localization.Row) int { return strings.Compare(left.Tag, right.Tag) }) labels := make([]string, 0, len(ordered)) for _, row := range ordered { label := row.Tag if !row.Enabled { label += " (off)" } if row.SocialLocale != nil { label += " [" + *row.SocialLocale + "]" } labels = append(labels, label) } return strings.Join(labels, ", ") } func (c UpdateSiteLanguagesCommand) change() localization.Change { entries := make([]localization.Entry, 0, len(c.Languages)) for _, entry := range c.Languages { entries = append(entries, localization.Entry{Tag: entry.Lang, Enabled: entry.Enabled, SocialLocale: entry.SocialLocale}) } return localization.Change{Default: c.Default, Languages: entries} }