feat: site languages, setup and site URL settings
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user