feat: site languages, setup and site URL settings

This commit is contained in:
Florian Sylvain committed 2026-11-01 22:13:02 +01:00
1 parent 0806733d5c
commit d9158025d4
106 files changed
+10184 -56

No files matched your search

@@ -0,0 +1,162 @@
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
}