From 789de2d5f6ab8f8981295ff8b18da84f5730bf67 Mon Sep 17 00:00:00 2001 From: Flashy78 <90150289+Flashy78@users.noreply.github.com> Date: Sun, 15 Oct 2023 23:26:43 -0700 Subject: [PATCH] Errors for performer/studio non-unique names (#4178) * Errors for performer/studio non-unique names --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- internal/api/resolver_mutation_performer.go | 12 ++++ internal/api/resolver_mutation_studio.go | 4 ++ pkg/performer/update.go | 76 +++++++++++++++++++++ pkg/studio/update.go | 7 ++ 4 files changed, 99 insertions(+) create mode 100644 pkg/performer/update.go diff --git a/internal/api/resolver_mutation_performer.go b/internal/api/resolver_mutation_performer.go index 9e40e7a01..e52270ec7 100644 --- a/internal/api/resolver_mutation_performer.go +++ b/internal/api/resolver_mutation_performer.go @@ -108,6 +108,10 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per if err := r.withTxn(ctx, func(ctx context.Context) error { qb := r.repository.Performer + if err := performer.EnsureNameUnique(ctx, newPerformer.Name, newPerformer.Disambiguation, qb); err != nil { + return err + } + err = qb.Create(ctx, &newPerformer) if err != nil { return err @@ -224,6 +228,10 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per return fmt.Errorf("performer with id %d not found", performerID) } + if err := performer.EnsureUpdateNameUnique(ctx, existing, updatedPerformer.Name, updatedPerformer.Disambiguation, qb); err != nil { + return err + } + if err := performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate); err != nil { return err } @@ -336,6 +344,10 @@ func (r *mutationResolver) BulkPerformerUpdate(ctx context.Context, input BulkPe return fmt.Errorf("performer with id %d not found", performerID) } + if err := performer.EnsureUpdateNameUnique(ctx, existing, updatedPerformer.Name, updatedPerformer.Disambiguation, qb); err != nil { + return err + } + err = performer.ValidateDeathDate(existing, input.Birthdate, input.DeathDate) if err != nil { return err diff --git a/internal/api/resolver_mutation_studio.go b/internal/api/resolver_mutation_studio.go index db314d261..cc76cc2d8 100644 --- a/internal/api/resolver_mutation_studio.go +++ b/internal/api/resolver_mutation_studio.go @@ -61,6 +61,10 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio if err := r.withTxn(ctx, func(ctx context.Context) error { qb := r.repository.Studio + if err := studio.EnsureStudioNameUnique(ctx, 0, newStudio.Name, qb); err != nil { + return err + } + if len(input.Aliases) > 0 { if err := studio.EnsureAliasesUnique(ctx, 0, input.Aliases, qb); err != nil { return err diff --git a/pkg/performer/update.go b/pkg/performer/update.go new file mode 100644 index 000000000..9fb5e83c8 --- /dev/null +++ b/pkg/performer/update.go @@ -0,0 +1,76 @@ +package performer + +import ( + "context" + "fmt" + + "github.com/stashapp/stash/pkg/models" +) + +type NameExistsError struct { + Name string + Disambiguation string +} + +func (e *NameExistsError) Error() string { + if e.Disambiguation != "" { + return fmt.Sprintf("performer with name '%s' and disambiguation '%s' already exists", e.Name, e.Disambiguation) + } + return fmt.Sprintf("performer with name '%s' already exists", e.Name) +} + +// EnsureNameUnique returns an error if the performer name and disambiguation provided +// is used by another performer +func EnsureNameUnique(ctx context.Context, name string, disambig string, qb models.PerformerReaderWriter) error { + performerFilter := models.PerformerFilterType{ + Name: &models.StringCriterionInput{ + Value: name, + Modifier: models.CriterionModifierEquals, + }, + } + + if disambig != "" { + performerFilter.Disambiguation = &models.StringCriterionInput{ + Value: disambig, + Modifier: models.CriterionModifierEquals, + } + } + + pp := 1 + findFilter := models.FindFilterType{ + PerPage: &pp, + } + + existing, _, err := qb.Query(ctx, &performerFilter, &findFilter) + if err != nil { + return err + } + + if len(existing) > 0 { + return &NameExistsError{ + Name: name, + Disambiguation: disambig, + } + } + + return nil +} + +// EnsureUpdateNameUnique performs the same check as EnsureNameUnique, but is used when modifying an existing performer. +func EnsureUpdateNameUnique(ctx context.Context, existing *models.Performer, name models.OptionalString, disambig models.OptionalString, qb models.PerformerReaderWriter) error { + newName := existing.Name + newDisambig := existing.Disambiguation + + if name.Set { + newName = name.Value + } + if disambig.Set { + newDisambig = disambig.Value + } + + if newName == existing.Name && newDisambig == existing.Disambiguation { + return nil + } + + return EnsureNameUnique(ctx, newName, newDisambig, qb) +} diff --git a/pkg/studio/update.go b/pkg/studio/update.go index a1a16a0c4..6f81b4464 100644 --- a/pkg/studio/update.go +++ b/pkg/studio/update.go @@ -80,6 +80,7 @@ type ValidateModifyReader interface { // 1. The studio exists locally // 2. The studio is not its own ancestor // 3. The studio's aliases are unique +// 4. The name is unique func ValidateModify(ctx context.Context, s models.StudioPartial, qb ValidateModifyReader) error { existing, err := qb.Find(ctx, s.ID) if err != nil { @@ -108,6 +109,12 @@ func ValidateModify(ctx context.Context, s models.StudioPartial, qb ValidateModi } } + if s.Name.Set && s.Name.Value != existing.Name { + if err := EnsureStudioNameUnique(ctx, 0, s.Name.Value, qb); err != nil { + return err + } + } + return nil }