163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
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
|
|
}
|