diff --git a/internal/api/input.go b/internal/api/input.go new file mode 100644 index 000000000..1a720e965 --- /dev/null +++ b/internal/api/input.go @@ -0,0 +1,35 @@ +package api + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/sliceutil/stringslice" +) + +// TODO - apply handleIDs to other resolvers that accept ID lists + +// handleIDList validates and converts a list of string IDs to integers +func handleIDList(idList []string, field string) ([]int, error) { + if err := validateIDList(idList); err != nil { + return nil, fmt.Errorf("validating %s: %w", field, err) + } + + ids, err := stringslice.StringSliceToIntSlice(idList) + if err != nil { + return nil, fmt.Errorf("converting %s: %w", field, err) + } + + return ids, nil +} + +// validateIDList returns an error if there are any duplicate ids in the list +func validateIDList(ids []string) error { + seen := make(map[string]struct{}) + for _, id := range ids { + if _, exists := seen[id]; exists { + return fmt.Errorf("duplicate id found: %s", id) + } + seen[id] = struct{}{} + } + return nil +} diff --git a/internal/api/resolver_query_find_folder.go b/internal/api/resolver_query_find_folder.go index d6832b7c9..60088e2a3 100644 --- a/internal/api/resolver_query_find_folder.go +++ b/internal/api/resolver_query_find_folder.go @@ -6,7 +6,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindFolder(ctx context.Context, id *string, path *string) (*models.Folder, error) { @@ -49,7 +48,7 @@ func (r *queryResolver) FindFolders( ) (ret *FindFoldersResultType, err error) { var folderIDs []models.FolderID if len(ids) > 0 { - folderIDsInt, err := stringslice.StringSliceToIntSlice(ids) + folderIDsInt, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_gallery.go b/internal/api/resolver_query_find_gallery.go index 724a48b12..09c0387cd 100644 --- a/internal/api/resolver_query_find_gallery.go +++ b/internal/api/resolver_query_find_gallery.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindGallery(ctx context.Context, id string) (ret *models.Gallery, err error) { @@ -25,7 +24,7 @@ func (r *queryResolver) FindGallery(ctx context.Context, id string) (ret *models } func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType, ids []string) (ret *FindGalleriesResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_group.go b/internal/api/resolver_query_find_group.go index 6f8a6c6ba..14d282379 100644 --- a/internal/api/resolver_query_find_group.go +++ b/internal/api/resolver_query_find_group.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindGroup(ctx context.Context, id string) (ret *models.Group, err error) { @@ -25,7 +24,7 @@ func (r *queryResolver) FindGroup(ctx context.Context, id string) (ret *models.G } func (r *queryResolver) FindGroups(ctx context.Context, groupFilter *models.GroupFilterType, filter *models.FindFilterType, ids []string) (ret *FindGroupsResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_image.go b/internal/api/resolver_query_find_image.go index 48b926345..90eaf33c0 100644 --- a/internal/api/resolver_query_find_image.go +++ b/internal/api/resolver_query_find_image.go @@ -7,7 +7,6 @@ import ( "github.com/99designs/gqlgen/graphql" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindImage(ctx context.Context, id *string, checksum *string) (*models.Image, error) { @@ -55,7 +54,7 @@ func (r *queryResolver) FindImages( filter *models.FindFilterType, ) (ret *FindImagesResultType, err error) { if len(ids) > 0 { - imageIds, err = stringslice.StringSliceToIntSlice(ids) + imageIds, err = handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_movie.go b/internal/api/resolver_query_find_movie.go index 2f80d6f59..c9dd3f846 100644 --- a/internal/api/resolver_query_find_movie.go +++ b/internal/api/resolver_query_find_movie.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.Group, err error) { @@ -25,7 +24,7 @@ func (r *queryResolver) FindMovie(ctx context.Context, id string) (ret *models.G } func (r *queryResolver) FindMovies(ctx context.Context, movieFilter *models.GroupFilterType, filter *models.FindFilterType, ids []string) (ret *FindMoviesResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_performer.go b/internal/api/resolver_query_find_performer.go index 150c99d20..7ea1f90c8 100644 --- a/internal/api/resolver_query_find_performer.go +++ b/internal/api/resolver_query_find_performer.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindPerformer(ctx context.Context, id string) (ret *models.Performer, err error) { @@ -26,7 +25,7 @@ func (r *queryResolver) FindPerformer(ctx context.Context, id string) (ret *mode func (r *queryResolver) FindPerformers(ctx context.Context, performerFilter *models.PerformerFilterType, filter *models.FindFilterType, performerIDs []int, ids []string) (ret *FindPerformersResultType, err error) { if len(ids) > 0 { - performerIDs, err = stringslice.StringSliceToIntSlice(ids) + performerIDs, err = handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_scene.go b/internal/api/resolver_query_find_scene.go index 44b5cfd5e..135ec43b7 100644 --- a/internal/api/resolver_query_find_scene.go +++ b/internal/api/resolver_query_find_scene.go @@ -9,7 +9,6 @@ import ( "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scene" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindScene(ctx context.Context, id *string, checksum *string) (*models.Scene, error) { @@ -83,7 +82,7 @@ func (r *queryResolver) FindScenes( filter *models.FindFilterType, ) (ret *FindScenesResultType, err error) { if len(ids) > 0 { - sceneIDs, err = stringslice.StringSliceToIntSlice(ids) + sceneIDs, err = handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_scene_marker.go b/internal/api/resolver_query_find_scene_marker.go index d3e47ce8d..e244bafef 100644 --- a/internal/api/resolver_query_find_scene_marker.go +++ b/internal/api/resolver_query_find_scene_marker.go @@ -4,11 +4,10 @@ import ( "context" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindSceneMarkers(ctx context.Context, sceneMarkerFilter *models.SceneMarkerFilterType, filter *models.FindFilterType, ids []string) (ret *FindSceneMarkersResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_studio.go b/internal/api/resolver_query_find_studio.go index 843592953..636772fe8 100644 --- a/internal/api/resolver_query_find_studio.go +++ b/internal/api/resolver_query_find_studio.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models.Studio, err error) { @@ -26,7 +25,7 @@ func (r *queryResolver) FindStudio(ctx context.Context, id string) (ret *models. } func (r *queryResolver) FindStudios(ctx context.Context, studioFilter *models.StudioFilterType, filter *models.FindFilterType, ids []string) (ret *FindStudiosResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/internal/api/resolver_query_find_tag.go b/internal/api/resolver_query_find_tag.go index f0e1d8b97..7dca0b481 100644 --- a/internal/api/resolver_query_find_tag.go +++ b/internal/api/resolver_query_find_tag.go @@ -5,7 +5,6 @@ import ( "strconv" "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/sliceutil/stringslice" ) func (r *queryResolver) FindTag(ctx context.Context, id string) (ret *models.Tag, err error) { @@ -25,7 +24,7 @@ func (r *queryResolver) FindTag(ctx context.Context, id string) (ret *models.Tag } func (r *queryResolver) FindTags(ctx context.Context, tagFilter *models.TagFilterType, filter *models.FindFilterType, ids []string) (ret *FindTagsResultType, err error) { - idInts, err := stringslice.StringSliceToIntSlice(ids) + idInts, err := handleIDList(ids, "ids") if err != nil { return nil, err } diff --git a/ui/v2.5/graphql/data/tag-slim.graphql b/ui/v2.5/graphql/data/tag-slim.graphql index 3c498539b..3aca74f16 100644 --- a/ui/v2.5/graphql/data/tag-slim.graphql +++ b/ui/v2.5/graphql/data/tag-slim.graphql @@ -6,4 +6,10 @@ fragment SlimTagData on Tag { image_path parent_count child_count + + stash_ids { + endpoint + stash_id + updated_at + } } diff --git a/ui/v2.5/graphql/data/tag.graphql b/ui/v2.5/graphql/data/tag.graphql index 2395f48bd..e0f432b67 100644 --- a/ui/v2.5/graphql/data/tag.graphql +++ b/ui/v2.5/graphql/data/tag.graphql @@ -50,4 +50,10 @@ fragment SelectTagData on Tag { name sort_name } + + stash_ids { + endpoint + stash_id + updated_at + } } diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx index 1670bcc7b..ef1a2e7e1 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx @@ -96,7 +96,9 @@ export const SceneMarkerForm: React.FC = ({ useEffect(() => { setPrimaryTag( - marker?.primary_tag ? { ...marker.primary_tag, aliases: [] } : undefined + marker?.primary_tag + ? { ...marker.primary_tag, aliases: [], stash_ids: [] } + : undefined ); }, [marker?.primary_tag]); @@ -105,6 +107,7 @@ export const SceneMarkerForm: React.FC = ({ marker?.tags.map((t) => ({ ...t, aliases: [], + stash_ids: [], })) ?? [] ); }, [marker?.tags]); diff --git a/ui/v2.5/src/components/Shared/ScrapeDialog/CreateLinkTagDialog.tsx b/ui/v2.5/src/components/Shared/ScrapeDialog/CreateLinkTagDialog.tsx index 73ba53dde..b5b19d913 100644 --- a/ui/v2.5/src/components/Shared/ScrapeDialog/CreateLinkTagDialog.tsx +++ b/ui/v2.5/src/components/Shared/ScrapeDialog/CreateLinkTagDialog.tsx @@ -45,9 +45,13 @@ export const CreateLinkTagDialog: React.FC<{ aliases: addAsAlias ? [...(existingTag.aliases || []), tag.name] : undefined, + // add stash id if applicable stash_ids: endpoint && tag.remote_site_id - ? [{ endpoint: endpoint!, stash_id: tag.remote_site_id }] + ? [ + ...(existingTag.stash_ids || []), + { endpoint: endpoint!, stash_id: tag.remote_site_id }, + ] : undefined, }; onClose({ update: updateInput }); diff --git a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx index f37754b37..4e7184fd6 100755 --- a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx @@ -467,7 +467,7 @@ const StashSearchResult: React.FC = ({ updateInput: GQL.TagUpdateInput ) { await updateTag(t, updateInput); - setTagIDs([...tagIDs, updateInput.id]); + setTagIDs(uniq([...tagIDs, updateInput.id])); } function showTagModal(t: GQL.ScrapedTag) { diff --git a/ui/v2.5/src/components/Tags/TagSelect.tsx b/ui/v2.5/src/components/Tags/TagSelect.tsx index 5b8da7a6d..c9ed83fea 100644 --- a/ui/v2.5/src/components/Tags/TagSelect.tsx +++ b/ui/v2.5/src/components/Tags/TagSelect.tsx @@ -38,7 +38,7 @@ export type SelectObject = { export type Tag = Pick< GQL.Tag, - "id" | "name" | "sort_name" | "aliases" | "image_path" + "id" | "name" | "sort_name" | "aliases" | "image_path" | "stash_ids" >; type Option = SelectOption; @@ -198,6 +198,7 @@ const _TagSelect: React.FC = (props) => { id, name, aliases: [], + stash_ids: [], }; }; diff --git a/ui/v2.5/src/hooks/tagsEdit.tsx b/ui/v2.5/src/hooks/tagsEdit.tsx index 7654081cf..ebd831feb 100644 --- a/ui/v2.5/src/hooks/tagsEdit.tsx +++ b/ui/v2.5/src/hooks/tagsEdit.tsx @@ -50,6 +50,7 @@ export function useTagsEdit( id: result.data.tagCreate.id, name: toCreate.name ?? "", aliases: [], + stash_ids: result.data.tagCreate.stash_ids, }, ]) ); @@ -93,6 +94,7 @@ export function useTagsEdit( id: p.stored_id!, name: p.name ?? "", aliases: [], + stash_ids: [], }; }) );