From 7b77b8986ff59b1dabf7db4795a1ee5daf6cc321 Mon Sep 17 00:00:00 2001 From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com> Date: Fri, 28 Jul 2023 02:36:00 +0200 Subject: [PATCH] Overhaul graphql client cache invalidation (#3912) * Update apollo client * Overhaul graphql client cache invalidation * Fix tagger studio link display update * Add graphql formatting --- graphql/documents/data/config.graphql | 2 +- graphql/documents/data/filter.graphql | 2 +- graphql/documents/data/gallery-slim.graphql | 2 +- graphql/documents/data/image.graphql | 2 +- graphql/documents/data/job.graphql | 2 +- graphql/documents/data/movie.graphql | 2 +- graphql/documents/data/scene.graphql | 2 +- graphql/documents/mutations/file.graphql | 2 +- graphql/documents/mutations/filter.graphql | 2 +- .../mutations/gallery-chapter.graphql | 38 +- graphql/documents/mutations/gallery.graphql | 38 +- graphql/documents/mutations/image.graphql | 44 +- graphql/documents/mutations/job.graphql | 4 +- graphql/documents/mutations/migration.graphql | 2 +- graphql/documents/mutations/performer.graphql | 14 +- graphql/documents/mutations/plugins.graphql | 6 +- .../documents/mutations/scene-marker.graphql | 62 +- graphql/documents/mutations/scene.graphql | 60 +- graphql/documents/mutations/stash-box.graphql | 4 +- graphql/documents/queries/dlna.graphql | 2 +- graphql/documents/queries/gallery.graphql | 5 +- graphql/documents/queries/image.graphql | 12 +- graphql/documents/queries/job.graphql | 6 +- graphql/documents/queries/legacy.graphql | 2 +- graphql/documents/queries/movie.graphql | 2 +- graphql/documents/queries/performer.graphql | 5 +- .../documents/queries/scene-marker.graphql | 7 +- graphql/documents/queries/scene.graphql | 17 +- .../queries/scrapers/freeones.graphql | 2 +- .../queries/scrapers/scrapers.graphql | 25 +- .../documents/queries/settings/config.graphql | 6 +- graphql/documents/queries/studio.graphql | 2 +- graphql/documents/queries/tag.graphql | 4 +- graphql/documents/subscriptions.graphql | 2 +- graphql/schema/schema.graphql | 289 +- graphql/schema/types/config.graphql | 477 ++-- graphql/schema/types/dlna.graphql | 38 +- graphql/schema/types/file.graphql | 132 +- graphql/schema/types/filters.graphql | 491 ++-- graphql/schema/types/gallery.graphql | 6 +- graphql/schema/types/image.graphql | 11 +- graphql/schema/types/logging.graphql | 16 +- graphql/schema/types/metadata.graphql | 149 +- graphql/schema/types/movie.graphql | 12 +- graphql/schema/types/performer.graphql | 8 +- graphql/schema/types/plugin.graphql | 49 +- graphql/schema/types/scalars.graphql | 3 +- graphql/schema/types/scene-marker-tag.graphql | 2 +- graphql/schema/types/scene-marker.graphql | 8 +- graphql/schema/types/scene.graphql | 46 +- graphql/schema/types/scraped-movie.graphql | 6 +- .../schema/types/scraped-performer.graphql | 10 +- graphql/schema/types/scraper.graphql | 107 +- graphql/schema/types/sql.graphql | 7 +- graphql/schema/types/stash-box.graphql | 12 +- graphql/schema/types/studio.graphql | 6 +- graphql/schema/types/tag.graphql | 5 +- graphql/stash-box/query.graphql | 4 +- ui/v2.5/package.json | 6 +- ui/v2.5/src/components/Tagger/context.tsx | 44 +- .../Tagger/scenes/StashSearchResult.tsx | 29 +- ui/v2.5/src/core/StashService.ts | 2489 +++++++++++------ ui/v2.5/src/core/createClient.ts | 101 +- ui/v2.5/yarn.lock | 21 +- 64 files changed, 3034 insertions(+), 1939 deletions(-) diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 3892cdac7..019f56a6e 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -149,7 +149,7 @@ fragment ConfigDefaultSettingsData on ConfigDefaultSettingsResult { scanGenerateThumbnails scanGenerateClipPreviews } - + identify { sources { source { diff --git a/graphql/documents/data/filter.graphql b/graphql/documents/data/filter.graphql index 39a3d080e..4c6236668 100644 --- a/graphql/documents/data/filter.graphql +++ b/graphql/documents/data/filter.graphql @@ -3,4 +3,4 @@ fragment SavedFilterData on SavedFilter { mode name filter -} \ No newline at end of file +} diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql index 9469c8486..7247b4f9a 100644 --- a/graphql/documents/data/gallery-slim.graphql +++ b/graphql/documents/data/gallery-slim.graphql @@ -17,7 +17,7 @@ fragment SlimGalleryData on Gallery { files { ...ImageFileData } - + paths { thumbnail } diff --git a/graphql/documents/data/image.graphql b/graphql/documents/data/image.graphql index 155c940e4..d55a81081 100644 --- a/graphql/documents/data/image.graphql +++ b/graphql/documents/data/image.graphql @@ -26,7 +26,7 @@ fragment ImageData on Image { studio { ...SlimStudioData } - + tags { ...SlimTagData } diff --git a/graphql/documents/data/job.graphql b/graphql/documents/data/job.graphql index f1f7c8529..d632bdd82 100644 --- a/graphql/documents/data/job.graphql +++ b/graphql/documents/data/job.graphql @@ -7,4 +7,4 @@ fragment JobData on Job { startTime endTime addTime -} \ No newline at end of file +} diff --git a/graphql/documents/data/movie.graphql b/graphql/documents/data/movie.graphql index 4aaf90bc0..1a72c1f24 100644 --- a/graphql/documents/data/movie.graphql +++ b/graphql/documents/data/movie.graphql @@ -10,7 +10,7 @@ fragment MovieData on Movie { studio { ...SlimStudioData } - + synopsis url front_image_path diff --git a/graphql/documents/data/scene.graphql b/graphql/documents/data/scene.graphql index 3f26856a3..d4f599c29 100644 --- a/graphql/documents/data/scene.graphql +++ b/graphql/documents/data/scene.graphql @@ -49,7 +49,7 @@ fragment SceneData on Scene { studio { ...SlimStudioData } - + movies { movie { ...MovieData diff --git a/graphql/documents/mutations/file.graphql b/graphql/documents/mutations/file.graphql index e63de9aeb..254a55126 100644 --- a/graphql/documents/mutations/file.graphql +++ b/graphql/documents/mutations/file.graphql @@ -1,3 +1,3 @@ mutation DeleteFiles($ids: [ID!]!) { deleteFiles(ids: $ids) -} \ No newline at end of file +} diff --git a/graphql/documents/mutations/filter.graphql b/graphql/documents/mutations/filter.graphql index f529f56e9..5d8013123 100644 --- a/graphql/documents/mutations/filter.graphql +++ b/graphql/documents/mutations/filter.graphql @@ -2,7 +2,7 @@ mutation SaveFilter($input: SaveFilterInput!) { saveFilter(input: $input) { ...SavedFilterData } -} +} mutation DestroySavedFilter($input: DestroyFilterInput!) { destroySavedFilter(input: $input) diff --git a/graphql/documents/mutations/gallery-chapter.graphql b/graphql/documents/mutations/gallery-chapter.graphql index 520aac8d3..b68bbed5d 100644 --- a/graphql/documents/mutations/gallery-chapter.graphql +++ b/graphql/documents/mutations/gallery-chapter.graphql @@ -1,27 +1,29 @@ mutation GalleryChapterCreate( - $title: String!, - $image_index: Int!, - $gallery_id: ID!) { - galleryChapterCreate(input: { - title: $title, - image_index: $image_index, - gallery_id: $gallery_id, - }) { + $title: String! + $image_index: Int! + $gallery_id: ID! +) { + galleryChapterCreate( + input: { title: $title, image_index: $image_index, gallery_id: $gallery_id } + ) { ...GalleryChapterData } } mutation GalleryChapterUpdate( - $id: ID!, - $title: String!, - $image_index: Int!, - $gallery_id: ID!) { - galleryChapterUpdate(input: { - id: $id, - title: $title, - image_index: $image_index, - gallery_id: $gallery_id, - }) { + $id: ID! + $title: String! + $image_index: Int! + $gallery_id: ID! +) { + galleryChapterUpdate( + input: { + id: $id + title: $title + image_index: $image_index + gallery_id: $gallery_id + } + ) { ...GalleryChapterData } } diff --git a/graphql/documents/mutations/gallery.graphql b/graphql/documents/mutations/gallery.graphql index 67ef74c3e..9f9fd1e0b 100644 --- a/graphql/documents/mutations/gallery.graphql +++ b/graphql/documents/mutations/gallery.graphql @@ -1,41 +1,45 @@ -mutation GalleryCreate( - $input: GalleryCreateInput!) { - +mutation GalleryCreate($input: GalleryCreateInput!) { galleryCreate(input: $input) { - ...GalleryData + ...GalleryData } } -mutation GalleryUpdate( - $input: GalleryUpdateInput!) { - +mutation GalleryUpdate($input: GalleryUpdateInput!) { galleryUpdate(input: $input) { - ...GalleryData + ...GalleryData } } -mutation BulkGalleryUpdate( - $input: BulkGalleryUpdateInput!) { - +mutation BulkGalleryUpdate($input: BulkGalleryUpdateInput!) { bulkGalleryUpdate(input: $input) { - ...GalleryData + ...GalleryData } } -mutation GalleriesUpdate($input : [GalleryUpdateInput!]!) { +mutation GalleriesUpdate($input: [GalleryUpdateInput!]!) { galleriesUpdate(input: $input) { ...GalleryData } } -mutation GalleryDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) { - galleryDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated}) +mutation GalleryDestroy( + $ids: [ID!]! + $delete_file: Boolean + $delete_generated: Boolean +) { + galleryDestroy( + input: { + ids: $ids + delete_file: $delete_file + delete_generated: $delete_generated + } + ) } mutation AddGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) { - addGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids}) + addGalleryImages(input: { gallery_id: $gallery_id, image_ids: $image_ids }) } mutation RemoveGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) { - removeGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids}) + removeGalleryImages(input: { gallery_id: $gallery_id, image_ids: $image_ids }) } diff --git a/graphql/documents/mutations/image.graphql b/graphql/documents/mutations/image.graphql index 26777e833..46f4dff10 100644 --- a/graphql/documents/mutations/image.graphql +++ b/graphql/documents/mutations/image.graphql @@ -1,27 +1,23 @@ -mutation ImageUpdate( - $input: ImageUpdateInput!) { - +mutation ImageUpdate($input: ImageUpdateInput!) { imageUpdate(input: $input) { - ...SlimImageData + ...SlimImageData } } -mutation BulkImageUpdate( - $input: BulkImageUpdateInput!) { - +mutation BulkImageUpdate($input: BulkImageUpdateInput!) { bulkImageUpdate(input: $input) { - ...SlimImageData + ...SlimImageData } } -mutation ImagesUpdate($input : [ImageUpdateInput!]!) { +mutation ImagesUpdate($input: [ImageUpdateInput!]!) { imagesUpdate(input: $input) { ...SlimImageData } } mutation ImageIncrementO($id: ID!) { - imageIncrementO(id: $id) + imageIncrementO(id: $id) } mutation ImageDecrementO($id: ID!) { @@ -32,10 +28,30 @@ mutation ImageResetO($id: ID!) { imageResetO(id: $id) } -mutation ImageDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) { - imageDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated}) +mutation ImageDestroy( + $id: ID! + $delete_file: Boolean + $delete_generated: Boolean +) { + imageDestroy( + input: { + id: $id + delete_file: $delete_file + delete_generated: $delete_generated + } + ) } -mutation ImagesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) { - imagesDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated}) +mutation ImagesDestroy( + $ids: [ID!]! + $delete_file: Boolean + $delete_generated: Boolean +) { + imagesDestroy( + input: { + ids: $ids + delete_file: $delete_file + delete_generated: $delete_generated + } + ) } diff --git a/graphql/documents/mutations/job.graphql b/graphql/documents/mutations/job.graphql index 4d64b5d31..f98b9aeb3 100644 --- a/graphql/documents/mutations/job.graphql +++ b/graphql/documents/mutations/job.graphql @@ -3,5 +3,5 @@ mutation StopJob($job_id: ID!) { } mutation StopAllJobs { - stopAllJobs -} \ No newline at end of file + stopAllJobs +} diff --git a/graphql/documents/mutations/migration.graphql b/graphql/documents/mutations/migration.graphql index edf483276..065e28257 100644 --- a/graphql/documents/mutations/migration.graphql +++ b/graphql/documents/mutations/migration.graphql @@ -4,4 +4,4 @@ mutation MigrateSceneScreenshots($input: MigrateSceneScreenshotsInput!) { mutation MigrateBlobs($input: MigrateBlobsInput!) { migrateBlobs(input: $input) -} \ No newline at end of file +} diff --git a/graphql/documents/mutations/performer.graphql b/graphql/documents/mutations/performer.graphql index 0e2ad9fa3..a4fa341ed 100644 --- a/graphql/documents/mutations/performer.graphql +++ b/graphql/documents/mutations/performer.graphql @@ -1,22 +1,16 @@ -mutation PerformerCreate( - $input: PerformerCreateInput!) { - +mutation PerformerCreate($input: PerformerCreateInput!) { performerCreate(input: $input) { - ...PerformerData + ...PerformerData } } -mutation PerformerUpdate( - $input: PerformerUpdateInput!) { - +mutation PerformerUpdate($input: PerformerUpdateInput!) { performerUpdate(input: $input) { ...PerformerData } } -mutation BulkPerformerUpdate( - $input: BulkPerformerUpdateInput!) { - +mutation BulkPerformerUpdate($input: BulkPerformerUpdateInput!) { bulkPerformerUpdate(input: $input) { ...PerformerData } diff --git a/graphql/documents/mutations/plugins.graphql b/graphql/documents/mutations/plugins.graphql index b989c7ca6..d964bd6b2 100644 --- a/graphql/documents/mutations/plugins.graphql +++ b/graphql/documents/mutations/plugins.graphql @@ -2,6 +2,10 @@ mutation ReloadPlugins { reloadPlugins } -mutation RunPluginTask($plugin_id: ID!, $task_name: String!, $args: [PluginArgInput!]) { +mutation RunPluginTask( + $plugin_id: ID! + $task_name: String! + $args: [PluginArgInput!] +) { runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args) } diff --git a/graphql/documents/mutations/scene-marker.graphql b/graphql/documents/mutations/scene-marker.graphql index 9bd58654e..fb4c97444 100644 --- a/graphql/documents/mutations/scene-marker.graphql +++ b/graphql/documents/mutations/scene-marker.graphql @@ -1,41 +1,45 @@ mutation SceneMarkerCreate( - $title: String!, - $seconds: Float!, - $scene_id: ID!, - $primary_tag_id: ID!, - $tag_ids: [ID!] = []) { - - sceneMarkerCreate(input: { - title: $title, - seconds: $seconds, - scene_id: $scene_id, - primary_tag_id: $primary_tag_id, - tag_ids: $tag_ids - }) { + $title: String! + $seconds: Float! + $scene_id: ID! + $primary_tag_id: ID! + $tag_ids: [ID!] = [] +) { + sceneMarkerCreate( + input: { + title: $title + seconds: $seconds + scene_id: $scene_id + primary_tag_id: $primary_tag_id + tag_ids: $tag_ids + } + ) { ...SceneMarkerData } } mutation SceneMarkerUpdate( - $id: ID!, - $title: String!, - $seconds: Float!, - $scene_id: ID!, - $primary_tag_id: ID!, - $tag_ids: [ID!] = []) { - - sceneMarkerUpdate(input: { - id: $id, - title: $title, - seconds: $seconds, - scene_id: $scene_id, - primary_tag_id: $primary_tag_id, - tag_ids: $tag_ids - }) { + $id: ID! + $title: String! + $seconds: Float! + $scene_id: ID! + $primary_tag_id: ID! + $tag_ids: [ID!] = [] +) { + sceneMarkerUpdate( + input: { + id: $id + title: $title + seconds: $seconds + scene_id: $scene_id + primary_tag_id: $primary_tag_id + tag_ids: $tag_ids + } + ) { ...SceneMarkerData } } mutation SceneMarkerDestroy($id: ID!) { sceneMarkerDestroy(id: $id) -} \ No newline at end of file +} diff --git a/graphql/documents/mutations/scene.graphql b/graphql/documents/mutations/scene.graphql index 8da4b3bd9..73153c4a6 100644 --- a/graphql/documents/mutations/scene.graphql +++ b/graphql/documents/mutations/scene.graphql @@ -1,43 +1,45 @@ -mutation SceneCreate( - $input: SceneCreateInput!) { - +mutation SceneCreate($input: SceneCreateInput!) { sceneCreate(input: $input) { ...SceneData } } -mutation SceneUpdate( - $input: SceneUpdateInput!) { - +mutation SceneUpdate($input: SceneUpdateInput!) { sceneUpdate(input: $input) { ...SceneData } } -mutation BulkSceneUpdate( - $input: BulkSceneUpdateInput!) { - +mutation BulkSceneUpdate($input: BulkSceneUpdateInput!) { bulkSceneUpdate(input: $input) { ...SceneData } } -mutation ScenesUpdate($input : [SceneUpdateInput!]!) { +mutation ScenesUpdate($input: [SceneUpdateInput!]!) { scenesUpdate(input: $input) { ...SceneData } } -mutation SceneSaveActivity($id: ID!, $resume_time: Float, $playDuration: Float) { - sceneSaveActivity(id: $id, resume_time: $resume_time, playDuration: $playDuration) +mutation SceneSaveActivity( + $id: ID! + $resume_time: Float + $playDuration: Float +) { + sceneSaveActivity( + id: $id + resume_time: $resume_time + playDuration: $playDuration + ) } mutation SceneIncrementPlayCount($id: ID!) { - sceneIncrementPlayCount(id: $id) + sceneIncrementPlayCount(id: $id) } mutation SceneIncrementO($id: ID!) { - sceneIncrementO(id: $id) + sceneIncrementO(id: $id) } mutation SceneDecrementO($id: ID!) { @@ -48,12 +50,32 @@ mutation SceneResetO($id: ID!) { sceneResetO(id: $id) } -mutation SceneDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) { - sceneDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated}) +mutation SceneDestroy( + $id: ID! + $delete_file: Boolean + $delete_generated: Boolean +) { + sceneDestroy( + input: { + id: $id + delete_file: $delete_file + delete_generated: $delete_generated + } + ) } -mutation ScenesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) { - scenesDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated}) +mutation ScenesDestroy( + $ids: [ID!]! + $delete_file: Boolean + $delete_generated: Boolean +) { + scenesDestroy( + input: { + ids: $ids + delete_file: $delete_file + delete_generated: $delete_generated + } + ) } mutation SceneGenerateScreenshot($id: ID!, $at: Float) { @@ -68,4 +90,4 @@ mutation SceneMerge($input: SceneMergeInput!) { sceneMerge(input: $input) { id } -} \ No newline at end of file +} diff --git a/graphql/documents/mutations/stash-box.graphql b/graphql/documents/mutations/stash-box.graphql index 55c508737..8821853be 100644 --- a/graphql/documents/mutations/stash-box.graphql +++ b/graphql/documents/mutations/stash-box.graphql @@ -1,4 +1,6 @@ -mutation SubmitStashBoxFingerprints($input: StashBoxFingerprintSubmissionInput!) { +mutation SubmitStashBoxFingerprints( + $input: StashBoxFingerprintSubmissionInput! +) { submitStashBoxFingerprints(input: $input) } diff --git a/graphql/documents/queries/dlna.graphql b/graphql/documents/queries/dlna.graphql index 9431abfa9..ee73238fe 100644 --- a/graphql/documents/queries/dlna.graphql +++ b/graphql/documents/queries/dlna.graphql @@ -8,4 +8,4 @@ query DLNAStatus { until } } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/gallery.graphql b/graphql/documents/queries/gallery.graphql index bfc034de4..22eb7281d 100644 --- a/graphql/documents/queries/gallery.graphql +++ b/graphql/documents/queries/gallery.graphql @@ -1,4 +1,7 @@ -query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType) { +query FindGalleries( + $filter: FindFilterType + $gallery_filter: GalleryFilterType +) { findGalleries(gallery_filter: $gallery_filter, filter: $filter) { count galleries { diff --git a/graphql/documents/queries/image.graphql b/graphql/documents/queries/image.graphql index 0f275138d..ee96d00d2 100644 --- a/graphql/documents/queries/image.graphql +++ b/graphql/documents/queries/image.graphql @@ -1,5 +1,13 @@ -query FindImages($filter: FindFilterType, $image_filter: ImageFilterType, $image_ids: [Int!]) { - findImages(filter: $filter, image_filter: $image_filter, image_ids: $image_ids) { +query FindImages( + $filter: FindFilterType + $image_filter: ImageFilterType + $image_ids: [Int!] +) { + findImages( + filter: $filter + image_filter: $image_filter + image_ids: $image_ids + ) { count megapixels filesize diff --git a/graphql/documents/queries/job.graphql b/graphql/documents/queries/job.graphql index 2578c4cd9..e13b67d13 100644 --- a/graphql/documents/queries/job.graphql +++ b/graphql/documents/queries/job.graphql @@ -5,7 +5,7 @@ query JobQueue { } query FindJob($input: FindJobInput!) { - findJob(input: $input) { - ...JobData - } + findJob(input: $input) { + ...JobData + } } diff --git a/graphql/documents/queries/legacy.graphql b/graphql/documents/queries/legacy.graphql index a93a590c2..446216af3 100644 --- a/graphql/documents/queries/legacy.graphql +++ b/graphql/documents/queries/legacy.graphql @@ -8,4 +8,4 @@ query MarkerWall($q: String) { markerWall(q: $q) { ...SceneMarkerData } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/movie.graphql b/graphql/documents/queries/movie.graphql index c22b61b5b..3fd347c73 100644 --- a/graphql/documents/queries/movie.graphql +++ b/graphql/documents/queries/movie.graphql @@ -11,4 +11,4 @@ query FindMovie($id: ID!) { findMovie(id: $id) { ...MovieData } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/performer.graphql b/graphql/documents/queries/performer.graphql index dec46bd2d..cc25752ac 100644 --- a/graphql/documents/queries/performer.graphql +++ b/graphql/documents/queries/performer.graphql @@ -1,4 +1,7 @@ -query FindPerformers($filter: FindFilterType, $performer_filter: PerformerFilterType) { +query FindPerformers( + $filter: FindFilterType + $performer_filter: PerformerFilterType +) { findPerformers(filter: $filter, performer_filter: $performer_filter) { count performers { diff --git a/graphql/documents/queries/scene-marker.graphql b/graphql/documents/queries/scene-marker.graphql index 21d43f80b..ab16611cf 100644 --- a/graphql/documents/queries/scene-marker.graphql +++ b/graphql/documents/queries/scene-marker.graphql @@ -1,8 +1,11 @@ -query FindSceneMarkers($filter: FindFilterType, $scene_marker_filter: SceneMarkerFilterType) { +query FindSceneMarkers( + $filter: FindFilterType + $scene_marker_filter: SceneMarkerFilterType +) { findSceneMarkers(filter: $filter, scene_marker_filter: $scene_marker_filter) { count scene_markers { ...SceneMarkerData } } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/scene.graphql b/graphql/documents/queries/scene.graphql index e62303dc7..9186e09ca 100644 --- a/graphql/documents/queries/scene.graphql +++ b/graphql/documents/queries/scene.graphql @@ -1,5 +1,13 @@ -query FindScenes($filter: FindFilterType, $scene_filter: SceneFilterType, $scene_ids: [Int!]) { - findScenes(filter: $filter, scene_filter: $scene_filter, scene_ids: $scene_ids) { +query FindScenes( + $filter: FindFilterType + $scene_filter: SceneFilterType + $scene_ids: [Int!] +) { + findScenes( + filter: $filter + scene_filter: $scene_filter + scene_ids: $scene_ids + ) { count filesize duration @@ -44,7 +52,10 @@ query FindSceneMarkerTags($id: ID!) { } } -query ParseSceneFilenames($filter: FindFilterType!, $config: SceneParserInput!) { +query ParseSceneFilenames( + $filter: FindFilterType! + $config: SceneParserInput! +) { parseSceneFilenames(filter: $filter, config: $config) { count results { diff --git a/graphql/documents/queries/scrapers/freeones.graphql b/graphql/documents/queries/scrapers/freeones.graphql index 6dfa700a1..995f19d23 100644 --- a/graphql/documents/queries/scrapers/freeones.graphql +++ b/graphql/documents/queries/scrapers/freeones.graphql @@ -1,3 +1,3 @@ query ScrapeFreeonesPerformers($q: String!) { scrapeFreeonesPerformerList(query: $q) -} \ No newline at end of file +} diff --git a/graphql/documents/queries/scrapers/scrapers.graphql b/graphql/documents/queries/scrapers/scrapers.graphql index 92c0bfd82..f043cf0a3 100644 --- a/graphql/documents/queries/scrapers/scrapers.graphql +++ b/graphql/documents/queries/scrapers/scrapers.graphql @@ -42,13 +42,19 @@ query ListMovieScrapers { } } -query ScrapeSinglePerformer($source: ScraperSourceInput!, $input: ScrapeSinglePerformerInput!) { +query ScrapeSinglePerformer( + $source: ScraperSourceInput! + $input: ScrapeSinglePerformerInput! +) { scrapeSinglePerformer(source: $source, input: $input) { ...ScrapedPerformerData } } -query ScrapeMultiPerformers($source: ScraperSourceInput!, $input: ScrapeMultiPerformersInput!) { +query ScrapeMultiPerformers( + $source: ScraperSourceInput! + $input: ScrapeMultiPerformersInput! +) { scrapeMultiPerformers(source: $source, input: $input) { ...ScrapedPerformerData } @@ -60,13 +66,19 @@ query ScrapePerformerURL($url: String!) { } } -query ScrapeSingleScene($source: ScraperSourceInput!, $input: ScrapeSingleSceneInput!) { +query ScrapeSingleScene( + $source: ScraperSourceInput! + $input: ScrapeSingleSceneInput! +) { scrapeSingleScene(source: $source, input: $input) { ...ScrapedSceneData } } -query ScrapeMultiScenes($source: ScraperSourceInput!, $input: ScrapeMultiScenesInput!) { +query ScrapeMultiScenes( + $source: ScraperSourceInput! + $input: ScrapeMultiScenesInput! +) { scrapeMultiScenes(source: $source, input: $input) { ...ScrapedSceneData } @@ -78,7 +90,10 @@ query ScrapeSceneURL($url: String!) { } } -query ScrapeSingleGallery($source: ScraperSourceInput!, $input: ScrapeSingleGalleryInput!) { +query ScrapeSingleGallery( + $source: ScraperSourceInput! + $input: ScrapeSingleGalleryInput! +) { scrapeSingleGallery(source: $source, input: $input) { ...ScrapedGalleryData } diff --git a/graphql/documents/queries/settings/config.graphql b/graphql/documents/queries/settings/config.graphql index 0a4b076d2..bfe883fab 100644 --- a/graphql/documents/queries/settings/config.graphql +++ b/graphql/documents/queries/settings/config.graphql @@ -6,9 +6,9 @@ query Configuration { query Directory($path: String) { directory(path: $path) { - path - parent - directories + path + parent + directories } } diff --git a/graphql/documents/queries/studio.graphql b/graphql/documents/queries/studio.graphql index d999343d2..592e0ac2b 100644 --- a/graphql/documents/queries/studio.graphql +++ b/graphql/documents/queries/studio.graphql @@ -1,4 +1,4 @@ -query FindStudios($filter: FindFilterType, $studio_filter: StudioFilterType ) { +query FindStudios($filter: FindFilterType, $studio_filter: StudioFilterType) { findStudios(filter: $filter, studio_filter: $studio_filter) { count studios { diff --git a/graphql/documents/queries/tag.graphql b/graphql/documents/queries/tag.graphql index 468b70d7d..bb69d4515 100644 --- a/graphql/documents/queries/tag.graphql +++ b/graphql/documents/queries/tag.graphql @@ -1,4 +1,4 @@ -query FindTags($filter: FindFilterType, $tag_filter: TagFilterType ) { +query FindTags($filter: FindFilterType, $tag_filter: TagFilterType) { findTags(filter: $filter, tag_filter: $tag_filter) { count tags { @@ -11,4 +11,4 @@ query FindTag($id: ID!) { findTag(id: $id) { ...TagData } -} \ No newline at end of file +} diff --git a/graphql/documents/subscriptions.graphql b/graphql/documents/subscriptions.graphql index fe5bcf3cc..7510e9f4a 100644 --- a/graphql/documents/subscriptions.graphql +++ b/graphql/documents/subscriptions.graphql @@ -19,4 +19,4 @@ subscription LoggingSubscribe { subscription ScanCompleteSubscribe { scanCompleteSubscribe -} \ No newline at end of file +} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index 2491061d4..9c4b22304 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -1,16 +1,20 @@ -"""The query root for this schema""" +"The query root for this schema" type Query { # Filters findSavedFilter(id: ID!): SavedFilter findSavedFilters(mode: FilterMode): [SavedFilter!]! findDefaultFilter(mode: FilterMode!): SavedFilter - """Find a scene by ID or Checksum""" + "Find a scene by ID or Checksum" findScene(id: ID, checksum: String): Scene findSceneByHash(input: SceneHashInput!): Scene - """A function which queries Scene objects""" - findScenes(scene_filter: SceneFilterType, scene_ids: [Int!], filter: FindFilterType): FindScenesResultType! + "A function which queries Scene objects" + findScenes( + scene_filter: SceneFilterType + scene_ids: [Int!] + filter: FindFilterType + ): FindScenesResultType! findScenesByPathRegex(filter: FindFilterType): FindScenesResultType! @@ -19,123 +23,180 @@ type Query { and the difference between their duration is smaller than durationDiff """ findDuplicateScenes( - distance: Int, - """Max difference in seconds between files in order to be considered for similarity matching. - Fractional seconds are ok: 0.5 will mean only files that have durations within 0.5 seconds between them will be matched based on PHash distance.""" + distance: Int + """ + Max difference in seconds between files in order to be considered for similarity matching. + Fractional seconds are ok: 0.5 will mean only files that have durations within 0.5 seconds between them will be matched based on PHash distance. + """ duration_diff: Float ): [[Scene!]!]! - """Return valid stream paths""" + "Return valid stream paths" sceneStreams(id: ID): [SceneStreamEndpoint!]! - parseSceneFilenames(filter: FindFilterType, config: SceneParserInput!): SceneParserResultType! + parseSceneFilenames( + filter: FindFilterType + config: SceneParserInput! + ): SceneParserResultType! - """A function which queries SceneMarker objects""" - findSceneMarkers(scene_marker_filter: SceneMarkerFilterType filter: FindFilterType): FindSceneMarkersResultType! + "A function which queries SceneMarker objects" + findSceneMarkers( + scene_marker_filter: SceneMarkerFilterType + filter: FindFilterType + ): FindSceneMarkersResultType! findImage(id: ID, checksum: String): Image - """A function which queries Scene objects""" - findImages(image_filter: ImageFilterType, image_ids: [Int!], filter: FindFilterType): FindImagesResultType! + "A function which queries Scene objects" + findImages( + image_filter: ImageFilterType + image_ids: [Int!] + filter: FindFilterType + ): FindImagesResultType! - """Find a performer by ID""" + "Find a performer by ID" findPerformer(id: ID!): Performer - """A function which queries Performer objects""" - findPerformers(performer_filter: PerformerFilterType, filter: FindFilterType): FindPerformersResultType! + "A function which queries Performer objects" + findPerformers( + performer_filter: PerformerFilterType + filter: FindFilterType + ): FindPerformersResultType! - """Find a studio by ID""" + "Find a studio by ID" findStudio(id: ID!): Studio - """A function which queries Studio objects""" - findStudios(studio_filter: StudioFilterType, filter: FindFilterType): FindStudiosResultType! + "A function which queries Studio objects" + findStudios( + studio_filter: StudioFilterType + filter: FindFilterType + ): FindStudiosResultType! - """Find a movie by ID""" + "Find a movie by ID" findMovie(id: ID!): Movie - """A function which queries Movie objects""" - findMovies(movie_filter: MovieFilterType, filter: FindFilterType): FindMoviesResultType! + "A function which queries Movie objects" + findMovies( + movie_filter: MovieFilterType + filter: FindFilterType + ): FindMoviesResultType! findGallery(id: ID!): Gallery - findGalleries(gallery_filter: GalleryFilterType, filter: FindFilterType): FindGalleriesResultType! + findGalleries( + gallery_filter: GalleryFilterType + filter: FindFilterType + ): FindGalleriesResultType! findTag(id: ID!): Tag - findTags(tag_filter: TagFilterType, filter: FindFilterType): FindTagsResultType! + findTags( + tag_filter: TagFilterType + filter: FindFilterType + ): FindTagsResultType! - """Retrieve random scene markers for the wall""" + "Retrieve random scene markers for the wall" markerWall(q: String): [SceneMarker!]! - """Retrieve random scenes for the wall""" + "Retrieve random scenes for the wall" sceneWall(q: String): [Scene!]! - """Get marker strings""" + "Get marker strings" markerStrings(q: String, sort: String): [MarkerStringsResultType]! - """Get stats""" + "Get stats" stats: StatsResultType! - """Organize scene markers by tag for a given scene ID""" + "Organize scene markers by tag for a given scene ID" sceneMarkerTags(scene_id: ID!): [SceneMarkerTag!]! logs: [LogEntry!]! # Scrapers - """List available scrapers""" + "List available scrapers" listScrapers(types: [ScrapeContentType!]!): [Scraper!]! - listPerformerScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [PERFORMER])") - listSceneScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [SCENE])") - listGalleryScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [GALLERY])") - listMovieScrapers: [Scraper!]! @deprecated(reason: "Use listScrapers(types: [MOVIE])") + listPerformerScrapers: [Scraper!]! + @deprecated(reason: "Use listScrapers(types: [PERFORMER])") + listSceneScrapers: [Scraper!]! + @deprecated(reason: "Use listScrapers(types: [SCENE])") + listGalleryScrapers: [Scraper!]! + @deprecated(reason: "Use listScrapers(types: [GALLERY])") + listMovieScrapers: [Scraper!]! + @deprecated(reason: "Use listScrapers(types: [MOVIE])") + "Scrape for a single scene" + scrapeSingleScene( + source: ScraperSourceInput! + input: ScrapeSingleSceneInput! + ): [ScrapedScene!]! + "Scrape for multiple scenes" + scrapeMultiScenes( + source: ScraperSourceInput! + input: ScrapeMultiScenesInput! + ): [[ScrapedScene!]!]! - """Scrape for a single scene""" - scrapeSingleScene(source: ScraperSourceInput!, input: ScrapeSingleSceneInput!): [ScrapedScene!]! - """Scrape for multiple scenes""" - scrapeMultiScenes(source: ScraperSourceInput!, input: ScrapeMultiScenesInput!): [[ScrapedScene!]!]! + "Scrape for a single performer" + scrapeSinglePerformer( + source: ScraperSourceInput! + input: ScrapeSinglePerformerInput! + ): [ScrapedPerformer!]! + "Scrape for multiple performers" + scrapeMultiPerformers( + source: ScraperSourceInput! + input: ScrapeMultiPerformersInput! + ): [[ScrapedPerformer!]!]! - """Scrape for a single performer""" - scrapeSinglePerformer(source: ScraperSourceInput!, input: ScrapeSinglePerformerInput!): [ScrapedPerformer!]! - """Scrape for multiple performers""" - scrapeMultiPerformers(source: ScraperSourceInput!, input: ScrapeMultiPerformersInput!): [[ScrapedPerformer!]!]! + "Scrape for a single gallery" + scrapeSingleGallery( + source: ScraperSourceInput! + input: ScrapeSingleGalleryInput! + ): [ScrapedGallery!]! - """Scrape for a single gallery""" - scrapeSingleGallery(source: ScraperSourceInput!, input: ScrapeSingleGalleryInput!): [ScrapedGallery!]! - - """Scrape for a single movie""" - scrapeSingleMovie(source: ScraperSourceInput!, input: ScrapeSingleMovieInput!): [ScrapedMovie!]! + "Scrape for a single movie" + scrapeSingleMovie( + source: ScraperSourceInput! + input: ScrapeSingleMovieInput! + ): [ScrapedMovie!]! "Scrapes content based on a URL" scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent - """Scrapes a complete performer record based on a URL""" + "Scrapes a complete performer record based on a URL" scrapePerformerURL(url: String!): ScrapedPerformer - """Scrapes a complete scene record based on a URL""" + "Scrapes a complete scene record based on a URL" scrapeSceneURL(url: String!): ScrapedScene - """Scrapes a complete gallery record based on a URL""" + "Scrapes a complete gallery record based on a URL" scrapeGalleryURL(url: String!): ScrapedGallery - """Scrapes a complete movie record based on a URL""" + "Scrapes a complete movie record based on a URL" scrapeMovieURL(url: String!): ScrapedMovie - """Scrape a list of performers based on name""" - scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]! @deprecated(reason: "use scrapeSinglePerformer") - """Scrapes a complete performer record based on a scrapePerformerList result""" - scrapePerformer(scraper_id: ID!, scraped_performer: ScrapedPerformerInput!): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer") - """Scrapes a complete scene record based on an existing scene""" - scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene @deprecated(reason: "use scrapeSingleScene") - """Scrapes a complete gallery record based on an existing gallery""" - scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery @deprecated(reason: "use scrapeSingleGallery") + "Scrape a list of performers based on name" + scrapePerformerList(scraper_id: ID!, query: String!): [ScrapedPerformer!]! + @deprecated(reason: "use scrapeSinglePerformer") + "Scrapes a complete performer record based on a scrapePerformerList result" + scrapePerformer( + scraper_id: ID! + scraped_performer: ScrapedPerformerInput! + ): ScrapedPerformer @deprecated(reason: "use scrapeSinglePerformer") + "Scrapes a complete scene record based on an existing scene" + scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene + @deprecated(reason: "use scrapeSingleScene") + "Scrapes a complete gallery record based on an existing gallery" + scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery + @deprecated(reason: "use scrapeSingleGallery") - """Scrape a list of performers from a query""" - scrapeFreeonesPerformerList(query: String!): [String!]! @deprecated(reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones") + "Scrape a list of performers from a query" + scrapeFreeonesPerformerList(query: String!): [String!]! + @deprecated( + reason: "use scrapeSinglePerformer with scraper_id = builtin_freeones" + ) # Plugins - """List loaded plugins""" + "List loaded plugins" plugins: [Plugin!] - """List available plugin operations""" + "List available plugin operations" pluginTasks: [PluginTask!] # Config - """Returns the current, complete configuration""" + "Returns the current, complete configuration" configuration: ConfigResult! - """Returns an array of paths for the given path""" + "Returns an array of paths for the given path" directory( "The directory path to list" - path: String, + path: String "Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..." locale: String = "en" ): Directory! @@ -182,20 +243,20 @@ type Mutation { scenesDestroy(input: ScenesDestroyInput!): Boolean! scenesUpdate(input: [SceneUpdateInput!]!): [Scene] - """Increments the o-counter for a scene. Returns the new value""" + "Increments the o-counter for a scene. Returns the new value" sceneIncrementO(id: ID!): Int! - """Decrements the o-counter for a scene. Returns the new value""" + "Decrements the o-counter for a scene. Returns the new value" sceneDecrementO(id: ID!): Int! - """Resets the o-counter for a scene to 0. Returns the new value""" + "Resets the o-counter for a scene to 0. Returns the new value" sceneResetO(id: ID!): Int! - """Sets the resume time point (if provided) and adds the provided duration to the scene's play duration""" + "Sets the resume time point (if provided) and adds the provided duration to the scene's play duration" sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean! - """Increments the play count for the scene. Returns the new play count value.""" + "Increments the play count for the scene. Returns the new play count value." sceneIncrementPlayCount(id: ID!): Int! - """Generates screenshot at specified time in seconds. Leave empty to generate default screenshot""" + "Generates screenshot at specified time in seconds. Leave empty to generate default screenshot" sceneGenerateScreenshot(id: ID!, at: Float): String! sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker @@ -210,11 +271,11 @@ type Mutation { imagesDestroy(input: ImagesDestroyInput!): Boolean! imagesUpdate(input: [ImageUpdateInput!]!): [Image] - """Increments the o-counter for an image. Returns the new value""" + "Increments the o-counter for an image. Returns the new value" imageIncrementO(id: ID!): Int! - """Decrements the o-counter for an image. Returns the new value""" + "Decrements the o-counter for an image. Returns the new value" imageDecrementO(id: ID!): Int! - """Resets the o-counter for a image to 0. Returns the new value""" + "Resets the o-counter for a image to 0. Returns the new value" imageResetO(id: ID!): Int! galleryCreate(input: GalleryCreateInput!): Gallery @@ -253,8 +314,10 @@ type Mutation { tagsDestroy(ids: [ID!]!): Boolean! tagsMerge(input: TagsMergeInput!): Tag - """Moves the given files to the given destination. Returns true if successful. - Either the destination_folder or destination_folder_id must be provided. If both are provided, the destination_folder_id takes precedence. + """ + Moves the given files to the given destination. Returns true if successful. + Either the destination_folder or destination_folder_id must be provided. + If both are provided, the destination_folder_id takes precedence. Destination folder must be a subfolder of one of the stash library paths. If provided, destination_basename must be a valid filename with an extension that matches one of the media extensions. @@ -268,94 +331,102 @@ type Mutation { destroySavedFilter(input: DestroyFilterInput!): Boolean! setDefaultFilter(input: SetDefaultFilterInput!): Boolean! - """Change general configuration options""" + "Change general configuration options" configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult! configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult! configureScraping(input: ConfigScrapingInput!): ConfigScrapingResult! - configureDefaults(input: ConfigDefaultSettingsInput!): ConfigDefaultSettingsResult! + configureDefaults( + input: ConfigDefaultSettingsInput! + ): ConfigDefaultSettingsResult! # overwrites the entire UI configuration configureUI(input: Map!): Map! # sets a single UI key value configureUISetting(key: String!, value: Any): Map! - """Generate and set (or clear) API key""" + "Generate and set (or clear) API key" generateAPIKey(input: GenerateAPIKeyInput!): String! - """Returns a link to download the result""" + "Returns a link to download the result" exportObjects(input: ExportObjectsInput!): String - """Performs an incremental import. Returns the job ID""" + "Performs an incremental import. Returns the job ID" importObjects(input: ImportObjectsInput!): ID! - """Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID""" + "Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID" metadataImport: ID! - """Start a full export. Outputs to the metadata directory. Returns the job ID""" + "Start a full export. Outputs to the metadata directory. Returns the job ID" metadataExport: ID! - """Start a scan. Returns the job ID""" + "Start a scan. Returns the job ID" metadataScan(input: ScanMetadataInput!): ID! - """Start generating content. Returns the job ID""" + "Start generating content. Returns the job ID" metadataGenerate(input: GenerateMetadataInput!): ID! - """Start auto-tagging. Returns the job ID""" + "Start auto-tagging. Returns the job ID" metadataAutoTag(input: AutoTagMetadataInput!): ID! - """Clean metadata. Returns the job ID""" + "Clean metadata. Returns the job ID" metadataClean(input: CleanMetadataInput!): ID! - """Identifies scenes using scrapers. Returns the job ID""" + "Identifies scenes using scrapers. Returns the job ID" metadataIdentify(input: IdentifyMetadataInput!): ID! - """Migrate generated files for the current hash naming""" + "Migrate generated files for the current hash naming" migrateHashNaming: ID! - """Migrates legacy scene screenshot files into the blob storage""" + "Migrates legacy scene screenshot files into the blob storage" migrateSceneScreenshots(input: MigrateSceneScreenshotsInput!): ID! - """Migrates blobs from the old storage system to the current one""" + "Migrates blobs from the old storage system to the current one" migrateBlobs(input: MigrateBlobsInput!): ID! - """Anonymise the database in a separate file. Optionally returns a link to download the database file""" + "Anonymise the database in a separate file. Optionally returns a link to download the database file" anonymiseDatabase(input: AnonymiseDatabaseInput!): String - """Reload scrapers""" + "Reload scrapers" reloadScrapers: Boolean! - """Run plugin task. Returns the job ID""" - runPluginTask(plugin_id: ID!, task_name: String!, args: [PluginArgInput!]): ID! + "Run plugin task. Returns the job ID" + runPluginTask( + plugin_id: ID! + task_name: String! + args: [PluginArgInput!] + ): ID! reloadPlugins: Boolean! stopJob(job_id: ID!): Boolean! stopAllJobs: Boolean! - """Submit fingerprints to stash-box instance""" - submitStashBoxFingerprints(input: StashBoxFingerprintSubmissionInput!): Boolean! + "Submit fingerprints to stash-box instance" + submitStashBoxFingerprints( + input: StashBoxFingerprintSubmissionInput! + ): Boolean! - """Submit scene as draft to stash-box instance""" + "Submit scene as draft to stash-box instance" submitStashBoxSceneDraft(input: StashBoxDraftSubmissionInput!): ID - """Submit performer as draft to stash-box instance""" + "Submit performer as draft to stash-box instance" submitStashBoxPerformerDraft(input: StashBoxDraftSubmissionInput!): ID - """Backup the database. Optionally returns a link to download the database file""" + "Backup the database. Optionally returns a link to download the database file" backupDatabase(input: BackupDatabaseInput!): String - """DANGEROUS: Execute an arbitrary SQL statement that returns rows.""" + "DANGEROUS: Execute an arbitrary SQL statement that returns rows." querySQL(sql: String!, args: [Any]): SQLQueryResult! - """DANGEROUS: Execute an arbitrary SQL statement without returning any rows.""" + "DANGEROUS: Execute an arbitrary SQL statement without returning any rows." execSQL(sql: String!, args: [Any]): SQLExecResult! - """Run batch performer tag task. Returns the job ID.""" + "Run batch performer tag task. Returns the job ID." stashBoxBatchPerformerTag(input: StashBoxBatchPerformerTagInput!): String! - """Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default""" + "Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default" enableDLNA(input: EnableDLNAInput!): Boolean! - """Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default""" + "Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default" disableDLNA(input: DisableDLNAInput!): Boolean! - """Enables an IP address for DLNA for an optional duration""" + "Enables an IP address for DLNA for an optional duration" addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean! - """Removes an IP address from the temporary DLNA whitelist""" + "Removes an IP address from the temporary DLNA whitelist" removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean! } type Subscription { - """Update from the metadata manager""" + "Update from the metadata manager" jobsSubscribe: JobStatusUpdate! loggingSubscribe: [LogEntry!]! diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 3bd341de9..a92bcc168 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -1,267 +1,311 @@ input SetupInput { - """Empty to indicate $HOME/.stash/config.yml default""" + "Empty to indicate $HOME/.stash/config.yml default" configLocation: String! stashes: [StashConfigInput!]! - """Empty to indicate default""" + "Empty to indicate default" databaseFile: String! - """Empty to indicate default""" + "Empty to indicate default" generatedLocation: String! - """Empty to indicate default""" + "Empty to indicate default" cacheLocation: String! - """Empty to indicate database storage for blobs""" + "Empty to indicate database storage for blobs" blobsLocation: String! } enum StreamingResolutionEnum { - "240p", LOW - "480p", STANDARD - "720p", STANDARD_HD - "1080p", FULL_HD - "4k", FOUR_K - "Original", ORIGINAL + "240p" + LOW + "480p" + STANDARD + "720p" + STANDARD_HD + "1080p" + FULL_HD + "4k" + FOUR_K + "Original" + ORIGINAL } enum PreviewPreset { - "X264_ULTRAFAST", ultrafast - "X264_VERYFAST", veryfast - "X264_FAST", fast - "X264_MEDIUM", medium - "X264_SLOW", slow - "X264_SLOWER", slower - "X264_VERYSLOW", veryslow + "X264_ULTRAFAST" + ultrafast + "X264_VERYFAST" + veryfast + "X264_FAST" + fast + "X264_MEDIUM" + medium + "X264_SLOW" + slow + "X264_SLOWER" + slower + "X264_VERYSLOW" + veryslow } enum HashAlgorithm { MD5 - "oshash", OSHASH + "oshash" + OSHASH } enum BlobsStorageType { # blobs are stored in the database - "Database", DATABASE + "Database" + DATABASE # blobs are stored in the filesystem under the configured blobs directory - "Filesystem", FILESYSTEM + "Filesystem" + FILESYSTEM } input ConfigGeneralInput { - """Array of file paths to content""" + "Array of file paths to content" stashes: [StashConfigInput!] - """Path to the SQLite database""" + "Path to the SQLite database" databasePath: String - """Path to backup directory""" + "Path to backup directory" backupDirectoryPath: String - """Path to generated files""" + "Path to generated files" generatedPath: String - """Path to import/export files""" + "Path to import/export files" metadataPath: String - """Path to scrapers""" + "Path to scrapers" scrapersPath: String - """Path to cache""" + "Path to cache" cachePath: String - """Path to blobs - required for filesystem blob storage""" + "Path to blobs - required for filesystem blob storage" blobsPath: String - """Where to store blobs""" + "Where to store blobs" blobsStorage: BlobsStorageType - """Whether to calculate MD5 checksums for scene video files""" + "Whether to calculate MD5 checksums for scene video files" calculateMD5: Boolean - """Hash algorithm to use for generated file naming""" + "Hash algorithm to use for generated file naming" videoFileNamingAlgorithm: HashAlgorithm - """Number of parallel tasks to start during scan/generate""" + "Number of parallel tasks to start during scan/generate" parallelTasks: Int - """Include audio stream in previews""" + "Include audio stream in previews" previewAudio: Boolean - """Number of segments in a preview file""" + "Number of segments in a preview file" previewSegments: Int - """Preview segment duration, in seconds""" + "Preview segment duration, in seconds" previewSegmentDuration: Float - """Duration of start of video to exclude when generating previews""" + "Duration of start of video to exclude when generating previews" previewExcludeStart: String - """Duration of end of video to exclude when generating previews""" + "Duration of end of video to exclude when generating previews" previewExcludeEnd: String - """Preset when generating preview""" + "Preset when generating preview" previewPreset: PreviewPreset - """Transcode Hardware Acceleration""" + "Transcode Hardware Acceleration" transcodeHardwareAcceleration: Boolean - """Max generated transcode size""" + "Max generated transcode size" maxTranscodeSize: StreamingResolutionEnum - """Max streaming transcode size""" + "Max streaming transcode size" maxStreamingTranscodeSize: StreamingResolutionEnum - - """ffmpeg transcode input args - injected before input file - These are applied to generated transcodes (previews and transcodes)""" + + """ + ffmpeg transcode input args - injected before input file + These are applied to generated transcodes (previews and transcodes) + """ transcodeInputArgs: [String!] - """ffmpeg transcode output args - injected before output file - These are applied to generated transcodes (previews and transcodes)""" + """ + ffmpeg transcode output args - injected before output file + These are applied to generated transcodes (previews and transcodes) + """ transcodeOutputArgs: [String!] - """ffmpeg stream input args - injected before input file - These are applied when live transcoding""" + """ + ffmpeg stream input args - injected before input file + These are applied when live transcoding + """ liveTranscodeInputArgs: [String!] - """ffmpeg stream output args - injected before output file - These are applied when live transcoding""" + """ + ffmpeg stream output args - injected before output file + These are applied when live transcoding + """ liveTranscodeOutputArgs: [String!] - """whether to include range in generated funscript heatmaps""" + "whether to include range in generated funscript heatmaps" drawFunscriptHeatmapRange: Boolean - """Write image thumbnails to disk when generating on the fly""" + "Write image thumbnails to disk when generating on the fly" writeImageThumbnails: Boolean - """Create Image Clips from Video extensions when Videos are disabled in Library""" + "Create Image Clips from Video extensions when Videos are disabled in Library" createImageClipsFromVideos: Boolean - """Username""" + "Username" username: String - """Password""" + "Password" password: String - """Maximum session cookie age""" + "Maximum session cookie age" maxSessionAge: Int - """Comma separated list of proxies to allow traffic from""" + "Comma separated list of proxies to allow traffic from" trustedProxies: [String!] @deprecated(reason: "no longer supported") - """Name of the log file""" + "Name of the log file" logFile: String - """Whether to also output to stderr""" + "Whether to also output to stderr" logOut: Boolean - """Minimum log level""" + "Minimum log level" logLevel: String - """Whether to log http access""" + "Whether to log http access" logAccess: Boolean - """True if galleries should be created from folders with images""" + "True if galleries should be created from folders with images" createGalleriesFromFolders: Boolean - """Regex used to identify images as gallery covers""" - galleryCoverRegex: String - """Array of video file extensions""" + "Regex used to identify images as gallery covers" + galleryCoverRegex: String + "Array of video file extensions" videoExtensions: [String!] - """Array of image file extensions""" + "Array of image file extensions" imageExtensions: [String!] - """Array of gallery zip file extensions""" + "Array of gallery zip file extensions" galleryExtensions: [String!] - """Array of file regexp to exclude from Video Scans""" + "Array of file regexp to exclude from Video Scans" excludes: [String!] - """Array of file regexp to exclude from Image Scans""" + "Array of file regexp to exclude from Image Scans" imageExcludes: [String!] - """Custom Performer Image Location""" + "Custom Performer Image Location" customPerformerImageLocation: String - """Scraper user agent string""" - scraperUserAgent: String @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead") - """Scraper CDP path. Path to chrome executable or remote address""" - scraperCDPPath: String @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead") - """Whether the scraper should check for invalid certificates""" - scraperCertCheck: Boolean @deprecated(reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead") - """Stash-box instances used for tagging""" + "Scraper user agent string" + scraperUserAgent: String + @deprecated( + reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead" + ) + "Scraper CDP path. Path to chrome executable or remote address" + scraperCDPPath: String + @deprecated( + reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead" + ) + "Whether the scraper should check for invalid certificates" + scraperCertCheck: Boolean + @deprecated( + reason: "use mutation ConfigureScraping(input: ConfigScrapingInput) instead" + ) + "Stash-box instances used for tagging" stashBoxes: [StashBoxInput!] - """Python path - resolved using path if unset""" + "Python path - resolved using path if unset" pythonPath: String } type ConfigGeneralResult { - """Array of file paths to content""" + "Array of file paths to content" stashes: [StashConfig!]! - """Path to the SQLite database""" + "Path to the SQLite database" databasePath: String! - """Path to backup directory""" + "Path to backup directory" backupDirectoryPath: String! - """Path to generated files""" + "Path to generated files" generatedPath: String! - """Path to import/export files""" + "Path to import/export files" metadataPath: String! - """Path to the config file used""" + "Path to the config file used" configFilePath: String! - """Path to scrapers""" + "Path to scrapers" scrapersPath: String! - """Path to cache""" + "Path to cache" cachePath: String! - """Path to blobs - required for filesystem blob storage""" + "Path to blobs - required for filesystem blob storage" blobsPath: String! - """Where to store blobs""" + "Where to store blobs" blobsStorage: BlobsStorageType! - """Whether to calculate MD5 checksums for scene video files""" + "Whether to calculate MD5 checksums for scene video files" calculateMD5: Boolean! - """Hash algorithm to use for generated file naming""" + "Hash algorithm to use for generated file naming" videoFileNamingAlgorithm: HashAlgorithm! - """Number of parallel tasks to start during scan/generate""" + "Number of parallel tasks to start during scan/generate" parallelTasks: Int! - """Include audio stream in previews""" + "Include audio stream in previews" previewAudio: Boolean! - """Number of segments in a preview file""" + "Number of segments in a preview file" previewSegments: Int! - """Preview segment duration, in seconds""" + "Preview segment duration, in seconds" previewSegmentDuration: Float! - """Duration of start of video to exclude when generating previews""" + "Duration of start of video to exclude when generating previews" previewExcludeStart: String! - """Duration of end of video to exclude when generating previews""" + "Duration of end of video to exclude when generating previews" previewExcludeEnd: String! - """Preset when generating preview""" + "Preset when generating preview" previewPreset: PreviewPreset! - """Transcode Hardware Acceleration""" + "Transcode Hardware Acceleration" transcodeHardwareAcceleration: Boolean! - """Max generated transcode size""" + "Max generated transcode size" maxTranscodeSize: StreamingResolutionEnum - """Max streaming transcode size""" + "Max streaming transcode size" maxStreamingTranscodeSize: StreamingResolutionEnum - """ffmpeg transcode input args - injected before input file - These are applied to generated transcodes (previews and transcodes)""" + """ + ffmpeg transcode input args - injected before input file + These are applied to generated transcodes (previews and transcodes) + """ transcodeInputArgs: [String!]! - """ffmpeg transcode output args - injected before output file - These are applied to generated transcodes (previews and transcodes)""" + """ + ffmpeg transcode output args - injected before output file + These are applied to generated transcodes (previews and transcodes) + """ transcodeOutputArgs: [String!]! - """ffmpeg stream input args - injected before input file - These are applied when live transcoding""" + """ + ffmpeg stream input args - injected before input file + These are applied when live transcoding + """ liveTranscodeInputArgs: [String!]! - """ffmpeg stream output args - injected before output file - These are applied when live transcoding""" + """ + ffmpeg stream output args - injected before output file + These are applied when live transcoding + """ liveTranscodeOutputArgs: [String!]! - """whether to include range in generated funscript heatmaps""" + "whether to include range in generated funscript heatmaps" drawFunscriptHeatmapRange: Boolean! - """Write image thumbnails to disk when generating on the fly""" + "Write image thumbnails to disk when generating on the fly" writeImageThumbnails: Boolean! - """Create Image Clips from Video extensions when Videos are disabled in Library""" + "Create Image Clips from Video extensions when Videos are disabled in Library" createImageClipsFromVideos: Boolean! - """API Key""" + "API Key" apiKey: String! - """Username""" + "Username" username: String! - """Password""" + "Password" password: String! - """Maximum session cookie age""" + "Maximum session cookie age" maxSessionAge: Int! - """Comma separated list of proxies to allow traffic from""" + "Comma separated list of proxies to allow traffic from" trustedProxies: [String!] @deprecated(reason: "no longer supported") - """Name of the log file""" + "Name of the log file" logFile: String - """Whether to also output to stderr""" + "Whether to also output to stderr" logOut: Boolean! - """Minimum log level""" + "Minimum log level" logLevel: String! - """Whether to log http access""" + "Whether to log http access" logAccess: Boolean! - """Array of video file extensions""" + "Array of video file extensions" videoExtensions: [String!]! - """Array of image file extensions""" + "Array of image file extensions" imageExtensions: [String!]! - """Array of gallery zip file extensions""" + "Array of gallery zip file extensions" galleryExtensions: [String!]! - """True if galleries should be created from folders with images""" + "True if galleries should be created from folders with images" createGalleriesFromFolders: Boolean! - """Regex used to identify images as gallery covers""" + "Regex used to identify images as gallery covers" galleryCoverRegex: String! - """Array of file regexp to exclude from Video Scans""" + "Array of file regexp to exclude from Video Scans" excludes: [String!]! - """Array of file regexp to exclude from Image Scans""" + "Array of file regexp to exclude from Image Scans" imageExcludes: [String!]! - """Custom Performer Image Location""" + "Custom Performer Image Location" customPerformerImageLocation: String - """Scraper user agent string""" - scraperUserAgent: String @deprecated(reason: "use ConfigResult.scraping instead") - """Scraper CDP path. Path to chrome executable or remote address""" - scraperCDPPath: String @deprecated(reason: "use ConfigResult.scraping instead") - """Whether the scraper should check for invalid certificates""" - scraperCertCheck: Boolean! @deprecated(reason: "use ConfigResult.scraping instead") - """Stash-box instances used for tagging""" + "Scraper user agent string" + scraperUserAgent: String + @deprecated(reason: "use ConfigResult.scraping instead") + "Scraper CDP path. Path to chrome executable or remote address" + scraperCDPPath: String + @deprecated(reason: "use ConfigResult.scraping instead") + "Whether the scraper should check for invalid certificates" + scraperCertCheck: Boolean! + @deprecated(reason: "use ConfigResult.scraping instead") + "Stash-box instances used for tagging" stashBoxes: [StashBox!]! - """Python path - resolved using path if unset""" + "Python path - resolved using path if unset" pythonPath: String! } @@ -302,64 +346,64 @@ type ConfigImageLightboxResult { } input ConfigInterfaceInput { - """Ordered list of items that should be shown in the menu""" + "Ordered list of items that should be shown in the menu" menuItems: [String!] - """Enable sound on mouseover previews""" + "Enable sound on mouseover previews" soundOnPreview: Boolean - - """Show title and tags in wall view""" + + "Show title and tags in wall view" wallShowTitle: Boolean - """Wall playback type""" + "Wall playback type" wallPlayback: String - """Show scene scrubber by default""" + "Show scene scrubber by default" showScrubber: Boolean - - """Maximum duration (in seconds) in which a scene video will loop in the scene player""" + + "Maximum duration (in seconds) in which a scene video will loop in the scene player" maximumLoopDuration: Int - """If true, video will autostart on load in the scene player""" + "If true, video will autostart on load in the scene player" autostartVideo: Boolean - """If true, video will autostart when loading from play random or play selected""" + "If true, video will autostart when loading from play random or play selected" autostartVideoOnPlaySelected: Boolean - """If true, next scene in playlist will be played at video end by default""" + "If true, next scene in playlist will be played at video end by default" continuePlaylistDefault: Boolean - - """If true, studio overlays will be shown as text instead of logo images""" + + "If true, studio overlays will be shown as text instead of logo images" showStudioAsText: Boolean - - """Custom CSS""" + + "Custom CSS" css: String cssEnabled: Boolean - """Custom Javascript""" + "Custom Javascript" javascript: String javascriptEnabled: Boolean - """Custom Locales""" + "Custom Locales" customLocales: String customLocalesEnabled: Boolean - - """Interface language""" + + "Interface language" language: String - """Slideshow Delay""" + "Slideshow Delay" slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay") - + imageLightbox: ConfigImageLightboxInput - - """Set to true to disable creating new objects via the dropdown menus""" + + "Set to true to disable creating new objects via the dropdown menus" disableDropdownCreate: ConfigDisableDropdownCreateInput - - """Handy Connection Key""" + + "Handy Connection Key" handyKey: String - """Funscript Time Offset""" + "Funscript Time Offset" funscriptOffset: Int - """Whether to use Stash Hosted Funscript""" + "Whether to use Stash Hosted Funscript" useStashHostedFunscript: Boolean - """True if we should not auto-open a browser window on startup""" + "True if we should not auto-open a browser window on startup" noBrowser: Boolean - """True if we should send notifications to the desktop""" + "True if we should send notifications to the desktop" notificationsEnabled: Boolean } @@ -371,111 +415,112 @@ type ConfigDisableDropdownCreate { } type ConfigInterfaceResult { - """Ordered list of items that should be shown in the menu""" + "Ordered list of items that should be shown in the menu" menuItems: [String!] - """Enable sound on mouseover previews""" + "Enable sound on mouseover previews" soundOnPreview: Boolean - """Show title and tags in wall view""" + "Show title and tags in wall view" wallShowTitle: Boolean - """Wall playback type""" + "Wall playback type" wallPlayback: String - """Show scene scrubber by default""" + "Show scene scrubber by default" showScrubber: Boolean - """Maximum duration (in seconds) in which a scene video will loop in the scene player""" + "Maximum duration (in seconds) in which a scene video will loop in the scene player" maximumLoopDuration: Int - """True if we should not auto-open a browser window on startup""" + "True if we should not auto-open a browser window on startup" noBrowser: Boolean - """True if we should send desktop notifications""" + "True if we should send desktop notifications" notificationsEnabled: Boolean - """If true, video will autostart on load in the scene player""" + "If true, video will autostart on load in the scene player" autostartVideo: Boolean - """If true, video will autostart when loading from play random or play selected""" + "If true, video will autostart when loading from play random or play selected" autostartVideoOnPlaySelected: Boolean - """If true, next scene in playlist will be played at video end by default""" + "If true, next scene in playlist will be played at video end by default" continuePlaylistDefault: Boolean - """If true, studio overlays will be shown as text instead of logo images""" + "If true, studio overlays will be shown as text instead of logo images" showStudioAsText: Boolean - """Custom CSS""" + "Custom CSS" css: String cssEnabled: Boolean - """Custom Javascript""" + "Custom Javascript" javascript: String javascriptEnabled: Boolean - """Custom Locales""" + "Custom Locales" customLocales: String customLocalesEnabled: Boolean - - """Interface language""" + + "Interface language" language: String - """Slideshow Delay""" + "Slideshow Delay" slideshowDelay: Int @deprecated(reason: "Use imageLightbox.slideshowDelay") imageLightbox: ConfigImageLightboxResult! - """Fields are true if creating via dropdown menus are disabled""" + "Fields are true if creating via dropdown menus are disabled" disableDropdownCreate: ConfigDisableDropdownCreate! - disabledDropdownCreate: ConfigDisableDropdownCreate! @deprecated(reason: "Use disableDropdownCreate") + disabledDropdownCreate: ConfigDisableDropdownCreate! + @deprecated(reason: "Use disableDropdownCreate") - """Handy Connection Key""" + "Handy Connection Key" handyKey: String - """Funscript Time Offset""" + "Funscript Time Offset" funscriptOffset: Int - """Whether to use Stash Hosted Funscript""" + "Whether to use Stash Hosted Funscript" useStashHostedFunscript: Boolean } input ConfigDLNAInput { serverName: String - """True if DLNA service should be enabled by default""" + "True if DLNA service should be enabled by default" enabled: Boolean - """List of IPs whitelisted for DLNA service""" + "List of IPs whitelisted for DLNA service" whitelistedIPs: [String!] - """List of interfaces to run DLNA on. Empty for all""" + "List of interfaces to run DLNA on. Empty for all" interfaces: [String!] - """Order to sort videos""" + "Order to sort videos" videoSortOrder: String } type ConfigDLNAResult { serverName: String! - """True if DLNA service should be enabled by default""" + "True if DLNA service should be enabled by default" enabled: Boolean! - """List of IPs whitelisted for DLNA service""" + "List of IPs whitelisted for DLNA service" whitelistedIPs: [String!]! - """List of interfaces to run DLNA on. Empty for all""" + "List of interfaces to run DLNA on. Empty for all" interfaces: [String!]! - """Order to sort videos""" + "Order to sort videos" videoSortOrder: String! } input ConfigScrapingInput { - """Scraper user agent string""" + "Scraper user agent string" scraperUserAgent: String - """Scraper CDP path. Path to chrome executable or remote address""" + "Scraper CDP path. Path to chrome executable or remote address" scraperCDPPath: String - """Whether the scraper should check for invalid certificates""" + "Whether the scraper should check for invalid certificates" scraperCertCheck: Boolean - """Tags blacklist during scraping""" + "Tags blacklist during scraping" excludeTagPatterns: [String!] } type ConfigScrapingResult { - """Scraper user agent string""" + "Scraper user agent string" scraperUserAgent: String - """Scraper CDP path. Path to chrome executable or remote address""" + "Scraper CDP path. Path to chrome executable or remote address" scraperCDPPath: String - """Whether the scraper should check for invalid certificates""" + "Whether the scraper should check for invalid certificates" scraperCertCheck: Boolean! - """Tags blacklist during scraping""" + "Tags blacklist during scraping" excludeTagPatterns: [String!]! } @@ -484,10 +529,10 @@ type ConfigDefaultSettingsResult { identify: IdentifyMetadataTaskOptions autoTag: AutoTagMetadataOptions generate: GenerateMetadataOptions - - """If true, delete file checkbox will be checked by default""" + + "If true, delete file checkbox will be checked by default" deleteFile: Boolean - """If true, delete generated supporting files checkbox will be checked by default""" + "If true, delete generated supporting files checkbox will be checked by default" deleteGenerated: Boolean } @@ -497,13 +542,13 @@ input ConfigDefaultSettingsInput { autoTag: AutoTagMetadataInput generate: GenerateMetadataInput - """If true, delete file checkbox will be checked by default""" + "If true, delete file checkbox will be checked by default" deleteFile: Boolean - """If true, delete generated files checkbox will be checked by default""" + "If true, delete generated files checkbox will be checked by default" deleteGenerated: Boolean } -"""All configuration settings""" +"All configuration settings" type ConfigResult { general: ConfigGeneralResult! interface: ConfigInterfaceResult! @@ -513,14 +558,14 @@ type ConfigResult { ui: Map! } -"""Directory structure of a path""" +"Directory structure of a path" type Directory { - path: String! - parent: String - directories: [String!]! + path: String! + parent: String + directories: [String!]! } -"""Stash configuration details""" +"Stash configuration details" input StashConfigInput { path: String! excludeVideo: Boolean! diff --git a/graphql/schema/types/dlna.graphql b/graphql/schema/types/dlna.graphql index 055e91a9e..f9c5ad2e3 100644 --- a/graphql/schema/types/dlna.graphql +++ b/graphql/schema/types/dlna.graphql @@ -1,35 +1,33 @@ - - type DLNAIP { - ipAddress: String! - """Time until IP will be no longer allowed/disallowed""" - until: Time + ipAddress: String! + "Time until IP will be no longer allowed/disallowed" + until: Time } type DLNAStatus { - running: Boolean! - """If not currently running, time until it will be started. If running, time until it will be stopped""" - until: Time - recentIPAddresses: [String!]! - allowedIPAddresses: [DLNAIP!]! + running: Boolean! + "If not currently running, time until it will be started. If running, time until it will be stopped" + until: Time + recentIPAddresses: [String!]! + allowedIPAddresses: [DLNAIP!]! } input EnableDLNAInput { - """Duration to enable, in minutes. 0 or null for indefinite.""" - duration: Int + "Duration to enable, in minutes. 0 or null for indefinite." + duration: Int } - + input DisableDLNAInput { - """Duration to enable, in minutes. 0 or null for indefinite.""" - duration: Int + "Duration to enable, in minutes. 0 or null for indefinite." + duration: Int } input AddTempDLNAIPInput { - address: String! - """Duration to enable, in minutes. 0 or null for indefinite.""" - duration: Int + address: String! + "Duration to enable, in minutes. 0 or null for indefinite." + duration: Int } input RemoveTempDLNAIPInput { - address: String! -} \ No newline at end of file + address: String! +} diff --git a/graphql/schema/types/file.graphql b/graphql/schema/types/file.graphql index 755d63215..b0388571e 100644 --- a/graphql/schema/types/file.graphql +++ b/graphql/schema/types/file.graphql @@ -1,111 +1,111 @@ type Fingerprint { - type: String! - value: String! + type: String! + value: String! } type Folder { - id: ID! - path: String! + id: ID! + path: String! - parent_folder_id: ID - zip_file_id: ID + parent_folder_id: ID + zip_file_id: ID - mod_time: Time! + mod_time: Time! - created_at: Time! - updated_at: Time! + created_at: Time! + updated_at: Time! } interface BaseFile { - id: ID! - path: String! - basename: String! + id: ID! + path: String! + basename: String! - parent_folder_id: ID! - zip_file_id: ID + parent_folder_id: ID! + zip_file_id: ID - mod_time: Time! - size: Int64! + mod_time: Time! + size: Int64! - fingerprints: [Fingerprint!]! + fingerprints: [Fingerprint!]! - created_at: Time! - updated_at: Time! + created_at: Time! + updated_at: Time! } type VideoFile implements BaseFile { - id: ID! - path: String! - basename: String! + id: ID! + path: String! + basename: String! - parent_folder_id: ID! - zip_file_id: ID + parent_folder_id: ID! + zip_file_id: ID - mod_time: Time! - size: Int64! + mod_time: Time! + size: Int64! - fingerprints: [Fingerprint!]! + fingerprints: [Fingerprint!]! - format: String! - width: Int! - height: Int! - duration: Float! - video_codec: String! - audio_codec: String! - frame_rate: Float! - bit_rate: Int! + format: String! + width: Int! + height: Int! + duration: Float! + video_codec: String! + audio_codec: String! + frame_rate: Float! + bit_rate: Int! - created_at: Time! - updated_at: Time! + created_at: Time! + updated_at: Time! } type ImageFile implements BaseFile { - id: ID! - path: String! - basename: String! + id: ID! + path: String! + basename: String! - parent_folder_id: ID! - zip_file_id: ID + parent_folder_id: ID! + zip_file_id: ID - mod_time: Time! - size: Int64! + mod_time: Time! + size: Int64! - fingerprints: [Fingerprint!]! + fingerprints: [Fingerprint!]! - width: Int! - height: Int! + width: Int! + height: Int! - created_at: Time! - updated_at: Time! + created_at: Time! + updated_at: Time! } union VisualFile = VideoFile | ImageFile type GalleryFile implements BaseFile { - id: ID! - path: String! - basename: String! + id: ID! + path: String! + basename: String! - parent_folder_id: ID! - zip_file_id: ID + parent_folder_id: ID! + zip_file_id: ID - mod_time: Time! - size: Int64! + mod_time: Time! + size: Int64! - fingerprints: [Fingerprint!]! + fingerprints: [Fingerprint!]! - created_at: Time! - updated_at: Time! + created_at: Time! + updated_at: Time! } input MoveFilesInput { - ids: [ID!]! - """valid for single or multiple file ids""" - destination_folder: String + ids: [ID!]! + "valid for single or multiple file ids" + destination_folder: String - """valid for single or multiple file ids""" - destination_folder_id: ID + "valid for single or multiple file ids" + destination_folder_id: ID - """valid only for single file id. If empty, existing basename is used""" - destination_basename: String + "valid only for single file id. If empty, existing basename is used" + destination_basename: String } diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index b80cf851b..13165fba8 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -6,28 +6,43 @@ enum SortDirectionEnum { input FindFilterType { q: String page: Int - """use per_page = -1 to indicate all results. Defaults to 25.""" + "use per_page = -1 to indicate all results. Defaults to 25." per_page: Int sort: String direction: SortDirectionEnum } enum ResolutionEnum { - "144p", VERY_LOW - "240p", LOW - "360p", R360P - "480p", STANDARD - "540p", WEB_HD - "720p", STANDARD_HD - "1080p", FULL_HD - "1440p", QUAD_HD - "1920p", VR_HD @deprecated(reason: "Use 4K instead") - "4K", FOUR_K - "5K", FIVE_K - "6K", SIX_K - "7K", SEVEN_K - "8K", EIGHT_K - "8K+", HUGE + "144p" + VERY_LOW + "240p" + LOW + "360p" + R360P + "480p" + STANDARD + "540p" + WEB_HD + "720p" + STANDARD_HD + "1080p" + FULL_HD + "1440p" + QUAD_HD + "1920p" + VR_HD @deprecated(reason: "Use 4K instead") + "4K" + FOUR_K + "5K" + FIVE_K + "6K" + SIX_K + "7K" + SEVEN_K + "8K" + EIGHT_K + "8K+" + HUGE } input ResolutionCriterionInput { @@ -37,13 +52,15 @@ input ResolutionCriterionInput { input PHashDuplicationCriterionInput { duplicated: Boolean - """Currently unimplemented""" + "Currently unimplemented" distance: Int } input StashIDCriterionInput { - """If present, this value is treated as a predicate. - That is, it will filter based on stash_ids with the matching endpoint""" + """ + If present, this value is treated as a predicate. + That is, it will filter based on stash_ids with the matching endpoint + """ endpoint: String stash_id: String modifier: CriterionModifier! @@ -58,104 +75,106 @@ input PerformerFilterType { disambiguation: StringCriterionInput details: StringCriterionInput - """Filter by favorite""" + "Filter by favorite" filter_favorites: Boolean - """Filter by birth year""" + "Filter by birth year" birth_year: IntCriterionInput - """Filter by age""" + "Filter by age" age: IntCriterionInput - """Filter by ethnicity""" + "Filter by ethnicity" ethnicity: StringCriterionInput - """Filter by country""" + "Filter by country" country: StringCriterionInput - """Filter by eye color""" + "Filter by eye color" eye_color: StringCriterionInput - """Filter by height""" - height: StringCriterionInput @deprecated(reason: "Use height_cm instead") - """Filter by height in cm""" + "Filter by height" + height: StringCriterionInput @deprecated(reason: "Use height_cm instead") + "Filter by height in cm" height_cm: IntCriterionInput - """Filter by measurements""" + "Filter by measurements" measurements: StringCriterionInput - """Filter by fake tits value""" + "Filter by fake tits value" fake_tits: StringCriterionInput - """Filter by penis length value""" + "Filter by penis length value" penis_length: FloatCriterionInput - """Filter by ciricumcision""" + "Filter by ciricumcision" circumcised: CircumcisionCriterionInput - """Filter by career length""" + "Filter by career length" career_length: StringCriterionInput - """Filter by tattoos""" + "Filter by tattoos" tattoos: StringCriterionInput - """Filter by piercings""" + "Filter by piercings" piercings: StringCriterionInput - """Filter by aliases""" + "Filter by aliases" aliases: StringCriterionInput - """Filter by gender""" + "Filter by gender" gender: GenderCriterionInput - """Filter to only include performers missing this property""" + "Filter to only include performers missing this property" is_missing: String - """Filter to only include performers with these tags""" + "Filter to only include performers with these tags" tags: HierarchicalMultiCriterionInput - """Filter by tag count""" + "Filter by tag count" tag_count: IntCriterionInput - """Filter by scene count""" + "Filter by scene count" scene_count: IntCriterionInput - """Filter by image count""" + "Filter by image count" image_count: IntCriterionInput - """Filter by gallery count""" + "Filter by gallery count" gallery_count: IntCriterionInput - """Filter by o count""" + "Filter by o count" o_counter: IntCriterionInput - """Filter by StashID""" - stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead") - """Filter by StashID""" + "Filter by StashID" + stash_id: StringCriterionInput + @deprecated(reason: "Use stash_id_endpoint instead") + "Filter by StashID" stash_id_endpoint: StashIDCriterionInput - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter by hair color""" + "Filter by hair color" hair_color: StringCriterionInput - """Filter by weight""" + "Filter by weight" weight: IntCriterionInput - """Filter by death year""" + "Filter by death year" death_year: IntCriterionInput - """Filter by studios where performer appears in scene/image/gallery""" + "Filter by studios where performer appears in scene/image/gallery" studios: HierarchicalMultiCriterionInput - """Filter by performers where performer appears with another performer in scene/image/gallery""" - performers: MultiCriterionInput - """Filter by autotag ignore value""" + "Filter by performers where performer appears with another performer in scene/image/gallery" + performers: MultiCriterionInput + "Filter by autotag ignore value" ignore_auto_tag: Boolean - """Filter by birthdate""" + "Filter by birthdate" birthdate: DateCriterionInput - """Filter by death date""" + "Filter by death date" death_date: DateCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } input SceneMarkerFilterType { - """Filter to only include scene markers with this tag""" + "Filter to only include scene markers with this tag" tag_id: ID @deprecated(reason: "use tags filter instead") - """Filter to only include scene markers with these tags""" + "Filter to only include scene markers with these tags" tags: HierarchicalMultiCriterionInput - """Filter to only include scene markers attached to a scene with these tags""" + "Filter to only include scene markers attached to a scene with these tags" scene_tags: HierarchicalMultiCriterionInput - """Filter to only include scene markers with these performers""" + "Filter to only include scene markers with these performers" performers: MultiCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput - """Filter by scene date""" + "Filter by scene date" scene_date: DateCriterionInput - """Filter by cscene reation time""" + "Filter by cscene reation time" scene_created_at: TimestampCriterionInput - """Filter by lscene ast update time""" + "Filter by lscene ast update time" scene_updated_at: TimestampCriterionInput } @@ -170,109 +189,111 @@ input SceneFilterType { details: StringCriterionInput director: StringCriterionInput - """Filter by file oshash""" + "Filter by file oshash" oshash: StringCriterionInput - """Filter by file checksum""" + "Filter by file checksum" checksum: StringCriterionInput - """Filter by file phash""" + "Filter by file phash" phash: StringCriterionInput @deprecated(reason: "Use phash_distance instead") - """Filter by file phash distance""" + "Filter by file phash distance" phash_distance: PhashDistanceCriterionInput - """Filter by path""" + "Filter by path" path: StringCriterionInput - """Filter by file count""" + "Filter by file count" file_count: IntCriterionInput - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter by organized""" + "Filter by organized" organized: Boolean - """Filter by o-counter""" + "Filter by o-counter" o_counter: IntCriterionInput - """Filter Scenes that have an exact phash match available""" + "Filter Scenes that have an exact phash match available" duplicated: PHashDuplicationCriterionInput - """Filter by resolution""" + "Filter by resolution" resolution: ResolutionCriterionInput - """Filter by video codec""" + "Filter by video codec" video_codec: StringCriterionInput - """Filter by audio codec""" + "Filter by audio codec" audio_codec: StringCriterionInput - """Filter by duration (in seconds)""" + "Filter by duration (in seconds)" duration: IntCriterionInput - """Filter to only include scenes which have markers. `true` or `false`""" + "Filter to only include scenes which have markers. `true` or `false`" has_markers: String - """Filter to only include scenes missing this property""" + "Filter to only include scenes missing this property" is_missing: String - """Filter to only include scenes with this studio""" + "Filter to only include scenes with this studio" studios: HierarchicalMultiCriterionInput - """Filter to only include scenes with this movie""" + "Filter to only include scenes with this movie" movies: MultiCriterionInput - """Filter to only include scenes with these tags""" + "Filter to only include scenes with these tags" tags: HierarchicalMultiCriterionInput - """Filter by tag count""" + "Filter by tag count" tag_count: IntCriterionInput - """Filter to only include scenes with performers with these tags""" + "Filter to only include scenes with performers with these tags" performer_tags: HierarchicalMultiCriterionInput - """Filter scenes that have performers that have been favorited""" + "Filter scenes that have performers that have been favorited" performer_favorite: Boolean - """Filter scenes by performer age at time of scene""" + "Filter scenes by performer age at time of scene" performer_age: IntCriterionInput - """Filter to only include scenes with these performers""" + "Filter to only include scenes with these performers" performers: MultiCriterionInput - """Filter by performer count""" + "Filter by performer count" performer_count: IntCriterionInput - """Filter by StashID""" - stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead") - """Filter by StashID""" + "Filter by StashID" + stash_id: StringCriterionInput + @deprecated(reason: "Use stash_id_endpoint instead") + "Filter by StashID" stash_id_endpoint: StashIDCriterionInput - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter by interactive""" + "Filter by interactive" interactive: Boolean - """Filter by InteractiveSpeed""" + "Filter by InteractiveSpeed" interactive_speed: IntCriterionInput - """Filter by captions""" + "Filter by captions" captions: StringCriterionInput - """Filter by resume time""" + "Filter by resume time" resume_time: IntCriterionInput - """Filter by play count""" + "Filter by play count" play_count: IntCriterionInput - """Filter by play duration (in seconds)""" + "Filter by play duration (in seconds)" play_duration: IntCriterionInput - """Filter by date""" + "Filter by date" date: DateCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } input MovieFilterType { - name: StringCriterionInput director: StringCriterionInput synopsis: StringCriterionInput - """Filter by duration (in seconds)""" + "Filter by duration (in seconds)" duration: IntCriterionInput - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter to only include movies with this studio""" + "Filter to only include movies with this studio" studios: HierarchicalMultiCriterionInput - """Filter to only include movies missing this property""" + "Filter to only include movies missing this property" is_missing: String - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter to only include movies where performer appears in a scene""" + "Filter to only include movies where performer appears in a scene" performers: MultiCriterionInput - """Filter by date""" + "Filter by date" date: DateCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } @@ -283,33 +304,35 @@ input StudioFilterType { name: StringCriterionInput details: StringCriterionInput - """Filter to only include studios with this parent studio""" + "Filter to only include studios with this parent studio" parents: MultiCriterionInput - """Filter by StashID""" - stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead") - """Filter by StashID""" + "Filter by StashID" + stash_id: StringCriterionInput + @deprecated(reason: "Use stash_id_endpoint instead") + "Filter by StashID" stash_id_endpoint: StashIDCriterionInput - """Filter to only include studios missing this property""" + "Filter to only include studios missing this property" is_missing: String - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter by scene count""" + "Filter by scene count" scene_count: IntCriterionInput - """Filter by image count""" + "Filter by image count" image_count: IntCriterionInput - """Filter by gallery count""" + "Filter by gallery count" gallery_count: IntCriterionInput - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter by studio aliases""" + "Filter by studio aliases" aliases: StringCriterionInput - """Filter by autotag ignore value""" + "Filter by autotag ignore value" ignore_auto_tag: Boolean - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } @@ -322,51 +345,52 @@ input GalleryFilterType { title: StringCriterionInput details: StringCriterionInput - """Filter by file checksum""" + "Filter by file checksum" checksum: StringCriterionInput - """Filter by path""" + "Filter by path" path: StringCriterionInput - """Filter by zip-file count""" + "Filter by zip-file count" file_count: IntCriterionInput - """Filter to only include galleries missing this property""" + "Filter to only include galleries missing this property" is_missing: String - """Filter to include/exclude galleries that were created from zip""" + "Filter to include/exclude galleries that were created from zip" is_zip: Boolean - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter by organized""" + "Filter by organized" organized: Boolean - """Filter by average image resolution""" + "Filter by average image resolution" average_resolution: ResolutionCriterionInput - """Filter to only include galleries that have chapters. `true` or `false`""" + "Filter to only include galleries that have chapters. `true` or `false`" has_chapters: String - """Filter to only include galleries with this studio""" + "Filter to only include galleries with this studio" studios: HierarchicalMultiCriterionInput - """Filter to only include galleries with these tags""" + "Filter to only include galleries with these tags" tags: HierarchicalMultiCriterionInput - """Filter by tag count""" + "Filter by tag count" tag_count: IntCriterionInput - """Filter to only include galleries with performers with these tags""" + "Filter to only include galleries with performers with these tags" performer_tags: HierarchicalMultiCriterionInput - """Filter to only include galleries with these performers""" + "Filter to only include galleries with these performers" performers: MultiCriterionInput - """Filter by performer count""" + "Filter by performer count" performer_count: IntCriterionInput - """Filter galleries that have performers that have been favorited""" + "Filter galleries that have performers that have been favorited" performer_favorite: Boolean - """Filter galleries by performer age at time of gallery""" + "Filter galleries by performer age at time of gallery" performer_age: IntCriterionInput - """Filter by number of images in this gallery""" + "Filter by number of images in this gallery" image_count: IntCriterionInput - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter by date""" + "Filter by date" date: DateCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } @@ -375,52 +399,52 @@ input TagFilterType { OR: TagFilterType NOT: TagFilterType - """Filter by tag name""" + "Filter by tag name" name: StringCriterionInput - """Filter by tag aliases""" + "Filter by tag aliases" aliases: StringCriterionInput - """Filter by tag description""" + "Filter by tag description" description: StringCriterionInput - """Filter to only include tags missing this property""" + "Filter to only include tags missing this property" is_missing: String - """Filter by number of scenes with this tag""" + "Filter by number of scenes with this tag" scene_count: IntCriterionInput - """Filter by number of images with this tag""" + "Filter by number of images with this tag" image_count: IntCriterionInput - """Filter by number of galleries with this tag""" + "Filter by number of galleries with this tag" gallery_count: IntCriterionInput - """Filter by number of performers with this tag""" + "Filter by number of performers with this tag" performer_count: IntCriterionInput - """Filter by number of markers with this tag""" + "Filter by number of markers with this tag" marker_count: IntCriterionInput - """Filter by parent tags""" + "Filter by parent tags" parents: HierarchicalMultiCriterionInput - """Filter by child tags""" + "Filter by child tags" children: HierarchicalMultiCriterionInput - """Filter by number of parent tags the tag has""" + "Filter by number of parent tags the tag has" parent_count: IntCriterionInput - """Filter by number f child tags the tag has""" + "Filter by number f child tags the tag has" child_count: IntCriterionInput - """Filter by autotag ignore value""" + "Filter by autotag ignore value" ignore_auto_tag: Boolean - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } @@ -431,77 +455,78 @@ input ImageFilterType { title: StringCriterionInput - """ Filter by image id""" + " Filter by image id" id: IntCriterionInput - """Filter by file checksum""" + "Filter by file checksum" checksum: StringCriterionInput - """Filter by path""" + "Filter by path" path: StringCriterionInput - """Filter by file count""" + "Filter by file count" file_count: IntCriterionInput - """Filter by rating""" - rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") + "Filter by rating" + rating: IntCriterionInput + @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 rating100: IntCriterionInput - """Filter by date""" + "Filter by date" date: DateCriterionInput - """Filter by url""" + "Filter by url" url: StringCriterionInput - """Filter by organized""" + "Filter by organized" organized: Boolean - """Filter by o-counter""" + "Filter by o-counter" o_counter: IntCriterionInput - """Filter by resolution""" + "Filter by resolution" resolution: ResolutionCriterionInput - """Filter to only include images missing this property""" + "Filter to only include images missing this property" is_missing: String - """Filter to only include images with this studio""" + "Filter to only include images with this studio" studios: HierarchicalMultiCriterionInput - """Filter to only include images with these tags""" + "Filter to only include images with these tags" tags: HierarchicalMultiCriterionInput - """Filter by tag count""" + "Filter by tag count" tag_count: IntCriterionInput - """Filter to only include images with performers with these tags""" + "Filter to only include images with performers with these tags" performer_tags: HierarchicalMultiCriterionInput - """Filter to only include images with these performers""" + "Filter to only include images with these performers" performers: MultiCriterionInput - """Filter by performer count""" + "Filter by performer count" performer_count: IntCriterionInput - """Filter images that have performers that have been favorited""" + "Filter images that have performers that have been favorited" performer_favorite: Boolean - """Filter to only include images with these galleries""" + "Filter to only include images with these galleries" galleries: MultiCriterionInput - """Filter by creation time""" + "Filter by creation time" created_at: TimestampCriterionInput - """Filter by last update time""" + "Filter by last update time" updated_at: TimestampCriterionInput } enum CriterionModifier { - """=""" - EQUALS, - """!=""" - NOT_EQUALS, - """>""" - GREATER_THAN, - """<""" - LESS_THAN, - """IS NULL""" - IS_NULL, - """IS NOT NULL""" - NOT_NULL, - """INCLUDES ALL""" - INCLUDES_ALL, - INCLUDES, - EXCLUDES, - """MATCHES REGEX""" - MATCHES_REGEX, - """NOT MATCHES REGEX""" - NOT_MATCHES_REGEX, - """>= AND <=""" - BETWEEN, - """< OR >""" - NOT_BETWEEN, + "=" + EQUALS + "!=" + NOT_EQUALS + ">" + GREATER_THAN + "<" + LESS_THAN + "IS NULL" + IS_NULL + "IS NOT NULL" + NOT_NULL + "INCLUDES ALL" + INCLUDES_ALL + INCLUDES + EXCLUDES + "MATCHES REGEX" + MATCHES_REGEX + "NOT MATCHES REGEX" + NOT_MATCHES_REGEX + ">= AND <=" + BETWEEN + "< OR >" + NOT_BETWEEN } input StringCriterionInput { @@ -531,7 +556,7 @@ input GenderCriterionInput { value: GenderEnum modifier: CriterionModifier! } - + input CircumcisionCriterionInput { value: [CircumisedEnum!] modifier: CriterionModifier! @@ -563,30 +588,30 @@ input PhashDistanceCriterionInput { } enum FilterMode { - SCENES, - PERFORMERS, - STUDIOS, - GALLERIES, - SCENE_MARKERS, - MOVIES, - TAGS, - IMAGES, + SCENES + PERFORMERS + STUDIOS + GALLERIES + SCENE_MARKERS + MOVIES + TAGS + IMAGES } type SavedFilter { id: ID! mode: FilterMode! name: String! - """JSON-encoded filter string""" + "JSON-encoded filter string" filter: String! } input SaveFilterInput { - """provide ID to overwrite existing filter""" + "provide ID to overwrite existing filter" id: ID mode: FilterMode! name: String! - """JSON-encoded filter string""" + "JSON-encoded filter string" filter: String! } @@ -596,6 +621,6 @@ input DestroyFilterInput { input SetDefaultFilterInput { mode: FilterMode! - """JSON-encoded filter string - null to clear""" + "JSON-encoded filter string - null to clear" filter: String } diff --git a/graphql/schema/types/gallery.graphql b/graphql/schema/types/gallery.graphql index 1f62ddd51..c2526fc62 100644 --- a/graphql/schema/types/gallery.graphql +++ b/graphql/schema/types/gallery.graphql @@ -1,4 +1,4 @@ -"""Gallery type""" +"Gallery type" type Gallery { id: ID! checksum: String! @deprecated(reason: "Use files.fingerprints") @@ -26,7 +26,7 @@ type Gallery { tags: [Tag!]! performers: [Performer!]! - """The images in the gallery""" + "The images in the gallery" images: [Image!]! @deprecated(reason: "Use findImages") cover: Image } @@ -87,7 +87,7 @@ input BulkGalleryUpdateInput { input GalleryDestroyInput { ids: [ID!]! """ - If true, then the zip file will be deleted if the gallery is zip-file-based. + If true, then the zip file will be deleted if the gallery is zip-file-based. If gallery is folder-based, then any files not associated with other galleries will be deleted, along with the folder, if it is not empty. """ diff --git a/graphql/schema/types/image.graphql b/graphql/schema/types/image.graphql index c2e34f085..5d13cbdd6 100644 --- a/graphql/schema/types/image.graphql +++ b/graphql/schema/types/image.graphql @@ -13,14 +13,13 @@ type Image { path: String! @deprecated(reason: "Use files.path") created_at: Time! updated_at: Time! - + file_mod_time: Time @deprecated(reason: "Use files.mod_time") file: ImageFileType! @deprecated(reason: "Use visual_files") files: [ImageFile!]! @deprecated(reason: "Use visual_files") visual_files: [VisualFile!]! paths: ImagePathsType! # Resolver - galleries: [Gallery!]! studio: Studio tags: [Tag!]! @@ -51,7 +50,7 @@ input ImageUpdateInput { organized: Boolean url: String date: String - + studio_id: ID performer_ids: [ID!] tag_ids: [ID!] @@ -71,7 +70,7 @@ input BulkImageUpdateInput { organized: Boolean url: String date: String - + studio_id: ID performer_ids: BulkUpdateIds tag_ids: BulkUpdateIds @@ -92,9 +91,9 @@ input ImagesDestroyInput { type FindImagesResultType { count: Int! - """Total megapixels of the images""" + "Total megapixels of the images" megapixels: Float! - """Total file size in bytes""" + "Total file size in bytes" filesize: Float! images: [Image!]! } diff --git a/graphql/schema/types/logging.graphql b/graphql/schema/types/logging.graphql index adab16401..4cfa2a64e 100644 --- a/graphql/schema/types/logging.graphql +++ b/graphql/schema/types/logging.graphql @@ -1,17 +1,17 @@ -"""Log entries""" +"Log entries" scalar Time enum LogLevel { - Trace - Debug - Info - Progress - Warning - Error + Trace + Debug + Info + Progress + Warning + Error } type LogEntry { time: Time! level: LogLevel! message: String! -} \ No newline at end of file +} diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index 7cd89202b..e56b373e8 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -10,31 +10,31 @@ input GenerateMetadataInput { markerImagePreviews: Boolean markerScreenshots: Boolean transcodes: Boolean - """Generate transcodes even if not required""" + "Generate transcodes even if not required" forceTranscodes: Boolean phashes: Boolean interactiveHeatmapsSpeeds: Boolean clipPreviews: Boolean - """scene ids to generate for""" + "scene ids to generate for" sceneIDs: [ID!] - """marker ids to generate for""" + "marker ids to generate for" markerIDs: [ID!] - """overwrite existing media""" + "overwrite existing media" overwrite: Boolean } input GeneratePreviewOptionsInput { - """Number of segments in a preview file""" + "Number of segments in a preview file" previewSegments: Int - """Preview segment duration, in seconds""" + "Preview segment duration, in seconds" previewSegmentDuration: Float - """Duration of start of video to exclude when generating previews""" + "Duration of start of video to exclude when generating previews" previewExcludeStart: String - """Duration of end of video to exclude when generating previews""" + "Duration of end of video to exclude when generating previews" previewExcludeEnd: String - """Preset when generating preview""" + "Preset when generating preview" previewPreset: PreviewPreset } @@ -54,15 +54,15 @@ type GenerateMetadataOptions { } type GeneratePreviewOptions { - """Number of segments in a preview file""" + "Number of segments in a preview file" previewSegments: Int - """Preview segment duration, in seconds""" + "Preview segment duration, in seconds" previewSegmentDuration: Float - """Duration of start of video to exclude when generating previews""" + "Duration of start of video to exclude when generating previews" previewExcludeStart: String - """Duration of end of video to exclude when generating previews""" + "Duration of end of video to exclude when generating previews" previewExcludeEnd: String - """Preset when generating preview""" + "Preset when generating preview" previewPreset: PreviewPreset } @@ -78,29 +78,29 @@ input ScanMetadataInput { # useFileMetadata is deprecated with the new file management system # if this functionality is desired, then we can make a built in scraper instead. - """Set name, date, details from metadata (if present)""" + "Set name, date, details from metadata (if present)" useFileMetadata: Boolean @deprecated(reason: "Not implemented") - # stripFileExtension is deprecated since we no longer set the title from the + # stripFileExtension is deprecated since we no longer set the title from the # filename - it is automatically returned if the object has no title. If this # functionality is desired, then we could make this an option to not include # the extension in the auto-generated title. - """Strip file extension from title""" + "Strip file extension from title" stripFileExtension: Boolean @deprecated(reason: "Not implemented") - """Generate covers during scan""" + "Generate covers during scan" scanGenerateCovers: Boolean - """Generate previews during scan""" + "Generate previews during scan" scanGeneratePreviews: Boolean - """Generate image previews during scan""" + "Generate image previews during scan" scanGenerateImagePreviews: Boolean - """Generate sprites during scan""" + "Generate sprites during scan" scanGenerateSprites: Boolean - """Generate phashes during scan""" + "Generate phashes during scan" scanGeneratePhashes: Boolean - """Generate image thumbnails during scan""" + "Generate image thumbnails during scan" scanGenerateThumbnails: Boolean - """Generate image clip previews during scan""" + "Generate image clip previews during scan" scanGenerateClipPreviews: Boolean "Filter options for the scan" @@ -108,62 +108,75 @@ input ScanMetadataInput { } type ScanMetadataOptions { - """Set name, date, details from metadata (if present)""" + "Set name, date, details from metadata (if present)" useFileMetadata: Boolean! @deprecated(reason: "Not implemented") - """Strip file extension from title""" + "Strip file extension from title" stripFileExtension: Boolean! @deprecated(reason: "Not implemented") - """Generate covers during scan""" + "Generate covers during scan" scanGenerateCovers: Boolean! - """Generate previews during scan""" + "Generate previews during scan" scanGeneratePreviews: Boolean! - """Generate image previews during scan""" + "Generate image previews during scan" scanGenerateImagePreviews: Boolean! - """Generate sprites during scan""" + "Generate sprites during scan" scanGenerateSprites: Boolean! - """Generate phashes during scan""" + "Generate phashes during scan" scanGeneratePhashes: Boolean! - """Generate image thumbnails during scan""" + "Generate image thumbnails during scan" scanGenerateThumbnails: Boolean! - """Generate image clip previews during scan""" + "Generate image clip previews during scan" scanGenerateClipPreviews: Boolean! } input CleanMetadataInput { paths: [String!] - - """Do a dry run. Don't delete any files""" + + "Do a dry run. Don't delete any files" dryRun: Boolean! } input AutoTagMetadataInput { - """Paths to tag, null for all files""" + "Paths to tag, null for all files" paths: [String!] - """IDs of performers to tag files with, or "*" for all""" + """ + IDs of performers to tag files with, or "*" for all + """ performers: [String!] - """IDs of studios to tag files with, or "*" for all""" + """ + IDs of studios to tag files with, or "*" for all + """ studios: [String!] - """IDs of tags to tag files with, or "*" for all""" + """ + IDs of tags to tag files with, or "*" for all + """ tags: [String!] } type AutoTagMetadataOptions { - """IDs of performers to tag files with, or "*" for all""" + """ + IDs of performers to tag files with, or "*" for all + """ performers: [String!] - """IDs of studios to tag files with, or "*" for all""" + """ + IDs of studios to tag files with, or "*" for all + """ studios: [String!] - """IDs of tags to tag files with, or "*" for all""" + """ + IDs of tags to tag files with, or "*" for all + """ tags: [String!] } enum IdentifyFieldStrategy { - """Never sets the field value""" + "Never sets the field value" IGNORE """ For multi-value fields, merge with existing. For single-value fields, ignore if already set """ MERGE - """Always replaces the value if a value is found. + """ + Always replaces the value if a value is found. For multi-value fields, any existing values are removed and replaced with the scraped values. """ @@ -173,44 +186,44 @@ enum IdentifyFieldStrategy { input IdentifyFieldOptionsInput { field: String! strategy: IdentifyFieldStrategy! - """creates missing objects if needed - only applicable for performers, tags and studios""" + "creates missing objects if needed - only applicable for performers, tags and studios" createMissing: Boolean } input IdentifyMetadataOptionsInput { - """any fields missing from here are defaulted to MERGE and createMissing false""" + "any fields missing from here are defaulted to MERGE and createMissing false" fieldOptions: [IdentifyFieldOptionsInput!] - """defaults to true if not provided""" + "defaults to true if not provided" setCoverImage: Boolean setOrganized: Boolean - """defaults to true if not provided""" + "defaults to true if not provided" includeMalePerformers: Boolean - """defaults to true if not provided""" + "defaults to true if not provided" skipMultipleMatches: Boolean - """tag to tag skipped multiple matches with""" + "tag to tag skipped multiple matches with" skipMultipleMatchTag: String - """defaults to true if not provided""" + "defaults to true if not provided" skipSingleNamePerformers: Boolean - """tag to tag skipped single name performers with""" + "tag to tag skipped single name performers with" skipSingleNamePerformerTag: String } input IdentifySourceInput { source: ScraperSourceInput! - """Options defined for a source override the defaults""" + "Options defined for a source override the defaults" options: IdentifyMetadataOptionsInput } input IdentifyMetadataInput { - """An ordered list of sources to identify items with. Only the first source that finds a match is used.""" + "An ordered list of sources to identify items with. Only the first source that finds a match is used." sources: [IdentifySourceInput!]! - """Options defined here override the configured defaults""" + "Options defined here override the configured defaults" options: IdentifyMetadataOptionsInput - """scene ids to identify""" + "scene ids to identify" sceneIDs: [ID!] - """paths of scenes to identify - ignored if scene ids are set""" + "paths of scenes to identify - ignored if scene ids are set" paths: [String!] } @@ -218,38 +231,38 @@ input IdentifyMetadataInput { type IdentifyFieldOptions { field: String! strategy: IdentifyFieldStrategy! - """creates missing objects if needed - only applicable for performers, tags and studios""" + "creates missing objects if needed - only applicable for performers, tags and studios" createMissing: Boolean } type IdentifyMetadataOptions { - """any fields missing from here are defaulted to MERGE and createMissing false""" + "any fields missing from here are defaulted to MERGE and createMissing false" fieldOptions: [IdentifyFieldOptions!] - """defaults to true if not provided""" + "defaults to true if not provided" setCoverImage: Boolean setOrganized: Boolean - """defaults to true if not provided""" + "defaults to true if not provided" includeMalePerformers: Boolean - """defaults to true if not provided""" + "defaults to true if not provided" skipMultipleMatches: Boolean - """tag to tag skipped multiple matches with""" + "tag to tag skipped multiple matches with" skipMultipleMatchTag: String - """defaults to true if not provided""" + "defaults to true if not provided" skipSingleNamePerformers: Boolean - """tag to tag skipped single name performers with""" + "tag to tag skipped single name performers with" skipSingleNamePerformerTag: String } type IdentifySource { source: ScraperSource! - """Options defined for a source override the defaults""" + "Options defined for a source override the defaults" options: IdentifyMetadataOptions } type IdentifyMetadataTaskOptions { - """An ordered list of sources to identify items with. Only the first source that finds a match is used.""" + "An ordered list of sources to identify items with. Only the first source that finds a match is used." sources: [IdentifySource!]! - """Options defined here override the configured defaults""" + "Options defined here override the configured defaults" options: IdentifyMetadataOptions } diff --git a/graphql/schema/types/movie.graphql b/graphql/schema/types/movie.graphql index 5220ad0da..d79dfe69e 100644 --- a/graphql/schema/types/movie.graphql +++ b/graphql/schema/types/movie.graphql @@ -3,7 +3,7 @@ type Movie { name: String! checksum: String! @deprecated(reason: "MD5 hash of name, use name directly") aliases: String - """Duration in seconds""" + "Duration in seconds" duration: Int date: String # rating expressed as 1-5 @@ -26,7 +26,7 @@ type Movie { input MovieCreateInput { name: String! aliases: String - """Duration in seconds""" + "Duration in seconds" duration: Int date: String # rating expressed as 1-5 @@ -37,9 +37,9 @@ input MovieCreateInput { director: String synopsis: String url: String - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" front_image: String - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" back_image: String } @@ -57,9 +57,9 @@ input MovieUpdateInput { director: String synopsis: String url: String - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" front_image: String - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" back_image: String } diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index 23e78a731..bbd08be0a 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -6,7 +6,7 @@ enum GenderEnum { INTERSEX NON_BINARY } - + enum CircumisedEnum { CUT UNCUT @@ -14,7 +14,7 @@ enum CircumisedEnum { type Performer { id: ID! - checksum: String @deprecated(reason: "Not used") + checksum: String @deprecated(reason: "Not used") name: String! disambiguation: String url: String @@ -87,7 +87,7 @@ input PerformerCreateInput { instagram: String favorite: Boolean tag_ids: [ID!] - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" image: String stash_ids: [StashIDInput!] # rating expressed as 1-5 @@ -127,7 +127,7 @@ input PerformerUpdateInput { instagram: String favorite: Boolean tag_ids: [ID!] - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" image: String stash_ids: [StashIDInput!] # rating expressed as 1-5 diff --git a/graphql/schema/types/plugin.graphql b/graphql/schema/types/plugin.graphql index 4828d7aae..b397e0d08 100644 --- a/graphql/schema/types/plugin.graphql +++ b/graphql/schema/types/plugin.graphql @@ -1,43 +1,42 @@ - type Plugin { - id: ID! - name: String! - description: String - url: String - version: String + id: ID! + name: String! + description: String + url: String + version: String - tasks: [PluginTask!] - hooks: [PluginHook!] + tasks: [PluginTask!] + hooks: [PluginHook!] } type PluginTask { - name: String! - description: String - plugin: Plugin! + name: String! + description: String + plugin: Plugin! } type PluginHook { - name: String! - description: String - hooks: [String!] - plugin: Plugin! + name: String! + description: String + hooks: [String!] + plugin: Plugin! } type PluginResult { - error: String - result: String + error: String + result: String } input PluginArgInput { - key: String! - value: PluginValueInput + key: String! + value: PluginValueInput } input PluginValueInput { - str: String - i: Int - b: Boolean - f: Float - o: [PluginArgInput!] - a: [PluginValueInput!] + str: String + i: Int + b: Boolean + f: Float + o: [PluginArgInput!] + a: [PluginValueInput!] } diff --git a/graphql/schema/types/scalars.graphql b/graphql/schema/types/scalars.graphql index 26d21bfba..2e4c59291 100644 --- a/graphql/schema/types/scalars.graphql +++ b/graphql/schema/types/scalars.graphql @@ -1,4 +1,3 @@ - """ Timestamp is a point in time. It is always output as RFC3339-compatible time points. It can be input as a RFC3339 string, or as "<4h" for "4 hours in the past" or ">5m" @@ -11,4 +10,4 @@ scalar Map scalar Any -scalar Int64 \ No newline at end of file +scalar Int64 diff --git a/graphql/schema/types/scene-marker-tag.graphql b/graphql/schema/types/scene-marker-tag.graphql index 4f8d571c6..42538eacc 100644 --- a/graphql/schema/types/scene-marker-tag.graphql +++ b/graphql/schema/types/scene-marker-tag.graphql @@ -1,4 +1,4 @@ type SceneMarkerTag { tag: Tag! scene_markers: [SceneMarker!]! -} \ No newline at end of file +} diff --git a/graphql/schema/types/scene-marker.graphql b/graphql/schema/types/scene-marker.graphql index 870af3b77..8b995c9d5 100644 --- a/graphql/schema/types/scene-marker.graphql +++ b/graphql/schema/types/scene-marker.graphql @@ -8,11 +8,11 @@ type SceneMarker { created_at: Time! updated_at: Time! - """The path to stream this marker""" + "The path to stream this marker" stream: String! # Resolver - """The path to the preview image for this marker""" + "The path to the preview image for this marker" preview: String! # Resolver - """The path to the screenshot image for this marker""" + "The path to the screenshot image for this marker" screenshot: String! # Resolver } @@ -42,4 +42,4 @@ type MarkerStringsResultType { count: Int! id: ID! title: String! -} \ No newline at end of file +} diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index ef538b22f..cb0831b0a 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -57,19 +57,18 @@ type Scene { created_at: Time! updated_at: Time! file_mod_time: Time - """The last time play count was updated""" + "The last time play count was updated" last_played_at: Time - """The time index a scene was left at""" + "The time index a scene was left at" resume_time: Float - """The total time a scene has spent playing""" + "The total time a scene has spent playing" play_duration: Float - """The number ot times a scene has been played""" + "The number ot times a scene has been played" play_count: Int file: SceneFileType! @deprecated(reason: "Use files") files: [VideoFile!]! paths: ScenePathsType! # Resolver - scene_markers: [SceneMarker!]! galleries: [Gallery!]! studio: Studio @@ -78,7 +77,7 @@ type Scene { performers: [Performer!]! stash_ids: [StashID!]! - """Return valid stream paths""" + "Return valid stream paths" sceneStreams: [SceneStreamEndpoint!]! } @@ -105,12 +104,15 @@ input SceneCreateInput { performer_ids: [ID!] movies: [SceneMovieInput!] tag_ids: [ID!] - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" cover_image: String stash_ids: [StashIDInput!] - """The first id will be assigned as primary. Files will be reassigned from - existing scenes if applicable. Files must not already be primary for another scene""" + """ + The first id will be assigned as primary. + Files will be reassigned from existing scenes if applicable. + Files must not already be primary for another scene. + """ file_ids: [ID!] } @@ -135,15 +137,15 @@ input SceneUpdateInput { performer_ids: [ID!] movies: [SceneMovieInput!] tag_ids: [ID!] - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" cover_image: String stash_ids: [StashIDInput!] - """The time index a scene was left at""" + "The time index a scene was left at" resume_time: Float - """The total time a scene has spent playing""" + "The total time a scene has spent playing" play_duration: Float - """The number ot times a scene has been played""" + "The number ot times a scene has been played" play_count: Int primary_file_id: ID @@ -179,7 +181,7 @@ input BulkSceneUpdateInput { gallery_ids: BulkUpdateIds performer_ids: BulkUpdateIds tag_ids: BulkUpdateIds - movie_ids: BulkUpdateIds + movie_ids: BulkUpdateIds } input SceneDestroyInput { @@ -196,17 +198,17 @@ input ScenesDestroyInput { type FindScenesResultType { count: Int! - """Total duration in seconds""" + "Total duration in seconds" duration: Float! - """Total file size in bytes""" + "Total file size in bytes" filesize: Float! scenes: [Scene!]! } input SceneParserInput { - ignoreWords: [String!], - whitespaceCharacters: String, - capitalizeTitle: Boolean, + ignoreWords: [String!] + whitespaceCharacters: String + capitalizeTitle: Boolean ignoreOrganized: Boolean } @@ -256,8 +258,10 @@ input AssignSceneFileInput { } input SceneMergeInput { - """If destination scene has no files, then the primary file of the - first source scene will be assigned as primary""" + """ + If destination scene has no files, then the primary file of the + first source scene will be assigned as primary + """ source: [ID!]! destination: ID! # values defined here will override values in the destination diff --git a/graphql/schema/types/scraped-movie.graphql b/graphql/schema/types/scraped-movie.graphql index 55efb693d..e3110b8e1 100644 --- a/graphql/schema/types/scraped-movie.graphql +++ b/graphql/schema/types/scraped-movie.graphql @@ -1,4 +1,4 @@ -"""A movie from a scraping operation...""" +"A movie from a scraping operation..." type ScrapedMovie { stored_id: ID name: String @@ -11,9 +11,9 @@ type ScrapedMovie { synopsis: String studio: ScrapedStudio - """This should be a base64 encoded data URL""" + "This should be a base64 encoded data URL" front_image: String - """This should be a base64 encoded data URL""" + "This should be a base64 encoded data URL" back_image: String } diff --git a/graphql/schema/types/scraped-performer.graphql b/graphql/schema/types/scraped-performer.graphql index a23b04fed..92ba94d32 100644 --- a/graphql/schema/types/scraped-performer.graphql +++ b/graphql/schema/types/scraped-performer.graphql @@ -1,6 +1,6 @@ -"""A performer from a scraping operation...""" +"A performer from a scraping operation..." type ScrapedPerformer { - """Set if performer matched""" + "Set if performer matched" stored_id: ID name: String disambiguation: String @@ -24,7 +24,7 @@ type ScrapedPerformer { aliases: String tags: [ScrapedTag!] - """This should be a base64 encoded data URL""" + "This should be a base64 encoded data URL" image: String @deprecated(reason: "use images instead") images: [String!] details: String @@ -35,7 +35,7 @@ type ScrapedPerformer { } input ScrapedPerformerInput { - """Set if performer matched""" + "Set if performer matched" stored_id: ID name: String disambiguation: String @@ -64,4 +64,4 @@ input ScrapedPerformerInput { hair_color: String weight: String remote_site_id: String -} \ No newline at end of file +} diff --git a/graphql/schema/types/scraper.graphql b/graphql/schema/types/scraper.graphql index f04eb2b37..d5ec7af17 100644 --- a/graphql/schema/types/scraper.graphql +++ b/graphql/schema/types/scraper.graphql @@ -1,9 +1,9 @@ enum ScrapeType { - """From text query""" + "From text query" NAME - """From existing object""" + "From existing object" FRAGMENT - """From URL""" + "From URL" URL } @@ -16,35 +16,35 @@ enum ScrapeContentType { } "Scraped Content is the forming union over the different scrapers" -union ScrapedContent = ScrapedStudio - | ScrapedTag - | ScrapedScene - | ScrapedGallery - | ScrapedMovie - | ScrapedPerformer +union ScrapedContent = + ScrapedStudio + | ScrapedTag + | ScrapedScene + | ScrapedGallery + | ScrapedMovie + | ScrapedPerformer type ScraperSpec { - """URLs matching these can be scraped with""" - urls: [String!] - supported_scrapes: [ScrapeType!]! + "URLs matching these can be scraped with" + urls: [String!] + supported_scrapes: [ScrapeType!]! } type Scraper { - id: ID! - name: String! - """Details for performer scraper""" - performer: ScraperSpec - """Details for scene scraper""" - scene: ScraperSpec - """Details for gallery scraper""" - gallery: ScraperSpec - """Details for movie scraper""" - movie: ScraperSpec + id: ID! + name: String! + "Details for performer scraper" + performer: ScraperSpec + "Details for scene scraper" + scene: ScraperSpec + "Details for gallery scraper" + gallery: ScraperSpec + "Details for movie scraper" + movie: ScraperSpec } - type ScrapedStudio { - """Set if studio matched""" + "Set if studio matched" stored_id: ID name: String! url: String @@ -54,7 +54,7 @@ type ScrapedStudio { } type ScrapedTag { - """Set if tag matched""" + "Set if tag matched" stored_id: ID name: String! } @@ -68,11 +68,10 @@ type ScrapedScene { urls: [String!] date: String - """This should be a base64 encoded data URL""" + "This should be a base64 encoded data URL" image: String file: SceneFileType # Resolver - studio: ScrapedStudio tags: [ScrapedTag!] performers: [ScrapedPerformer!] @@ -118,84 +117,84 @@ input ScrapedGalleryInput { } input ScraperSourceInput { - """Index of the configured stash-box instance to use. Should be unset if scraper_id is set""" + "Index of the configured stash-box instance to use. Should be unset if scraper_id is set" stash_box_index: Int @deprecated(reason: "use stash_box_endpoint") - """Stash-box endpoint""" + "Stash-box endpoint" stash_box_endpoint: String - """Scraper ID to scrape with. Should be unset if stash_box_index is set""" + "Scraper ID to scrape with. Should be unset if stash_box_index is set" scraper_id: ID } type ScraperSource { - """Index of the configured stash-box instance to use. Should be unset if scraper_id is set""" + "Index of the configured stash-box instance to use. Should be unset if scraper_id is set" stash_box_index: Int @deprecated(reason: "use stash_box_endpoint") - """Stash-box endpoint""" + "Stash-box endpoint" stash_box_endpoint: String - """Scraper ID to scrape with. Should be unset if stash_box_index is set""" + "Scraper ID to scrape with. Should be unset if stash_box_index is set" scraper_id: ID } input ScrapeSingleSceneInput { - """Instructs to query by string""" + "Instructs to query by string" query: String - """Instructs to query by scene fingerprints""" + "Instructs to query by scene fingerprints" scene_id: ID - """Instructs to query by scene fragment""" + "Instructs to query by scene fragment" scene_input: ScrapedSceneInput } input ScrapeMultiScenesInput { - """Instructs to query by scene fingerprints""" + "Instructs to query by scene fingerprints" scene_ids: [ID!] } input ScrapeSinglePerformerInput { - """Instructs to query by string""" + "Instructs to query by string" query: String - """Instructs to query by performer id""" + "Instructs to query by performer id" performer_id: ID - """Instructs to query by performer fragment""" + "Instructs to query by performer fragment" performer_input: ScrapedPerformerInput } input ScrapeMultiPerformersInput { - """Instructs to query by scene fingerprints""" + "Instructs to query by scene fingerprints" performer_ids: [ID!] } input ScrapeSingleGalleryInput { - """Instructs to query by string""" + "Instructs to query by string" query: String - """Instructs to query by gallery id""" + "Instructs to query by gallery id" gallery_id: ID - """Instructs to query by gallery fragment""" + "Instructs to query by gallery fragment" gallery_input: ScrapedGalleryInput } input ScrapeSingleMovieInput { - """Instructs to query by string""" + "Instructs to query by string" query: String - """Instructs to query by movie id""" + "Instructs to query by movie id" movie_id: ID - """Instructs to query by gallery fragment""" + "Instructs to query by gallery fragment" movie_input: ScrapedMovieInput } input StashBoxSceneQueryInput { - """Index of the configured stash-box instance to use""" + "Index of the configured stash-box instance to use" stash_box_index: Int! - """Instructs query by scene fingerprints""" + "Instructs query by scene fingerprints" scene_ids: [ID!] - """Query by query string""" + "Query by query string" q: String } input StashBoxPerformerQueryInput { - """Index of the configured stash-box instance to use""" + "Index of the configured stash-box instance to use" stash_box_index: Int! - """Instructs query by scene fingerprints""" + "Instructs query by scene fingerprints" performer_ids: [ID!] - """Query by query string""" + "Query by query string" q: String } @@ -210,7 +209,7 @@ type StashBoxFingerprint { duration: Int! } -"""If neither performer_ids nor performer_names are set, tag all performers""" +"If neither performer_ids nor performer_names are set, tag all performers" input StashBoxBatchPerformerTagInput { "Stash endpoint to use for the performer tagging" endpoint: Int! diff --git a/graphql/schema/types/sql.graphql b/graphql/schema/types/sql.graphql index 055c3a21c..53615d6f9 100644 --- a/graphql/schema/types/sql.graphql +++ b/graphql/schema/types/sql.graphql @@ -1,7 +1,7 @@ type SQLQueryResult { - """The column names, in the order they appear in the result set.""" + "The column names, in the order they appear in the result set." columns: [String!]! - """The returned rows.""" + "The returned rows." rows: [[Any]!]! } @@ -12,7 +12,8 @@ type SQLExecResult { """ rows_affected: Int64 """ - The integer generated by the database in response to a command. Typically this will be from an "auto increment" column when inserting a new row. + The integer generated by the database in response to a command. + Typically this will be from an "auto increment" column when inserting a new row. Not all databases support this feature, and the syntax of such statements varies. """ last_insert_id: Int64 diff --git a/graphql/schema/types/stash-box.graphql b/graphql/schema/types/stash-box.graphql index 7614a2fae..865311e4a 100644 --- a/graphql/schema/types/stash-box.graphql +++ b/graphql/schema/types/stash-box.graphql @@ -1,13 +1,13 @@ type StashBox { - endpoint: String! - api_key: String! - name: String! + endpoint: String! + api_key: String! + name: String! } input StashBoxInput { - endpoint: String! - api_key: String! - name: String! + endpoint: String! + api_key: String! + name: String! } type StashID { diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index 5516ac23c..20d9d9770 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -29,7 +29,7 @@ input StudioCreateInput { name: String! url: String parent_id: ID - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" image: String stash_ids: [StashIDInput!] # rating expressed as 1-5 @@ -45,8 +45,8 @@ input StudioUpdateInput { id: ID! name: String url: String - parent_id: ID, - """This should be a URL or a base64 encoded data URL""" + parent_id: ID + "This should be a URL or a base64 encoded data URL" image: String stash_ids: [StashIDInput!] # rating expressed as 1-5 diff --git a/graphql/schema/types/tag.graphql b/graphql/schema/types/tag.graphql index 3af621111..626085657 100644 --- a/graphql/schema/types/tag.graphql +++ b/graphql/schema/types/tag.graphql @@ -13,7 +13,6 @@ type Tag { image_count(depth: Int): Int! # Resolver gallery_count(depth: Int): Int! # Resolver performer_count(depth: Int): Int! # Resolver - parents: [Tag!]! children: [Tag!]! } @@ -24,7 +23,7 @@ input TagCreateInput { aliases: [String!] ignore_auto_tag: Boolean - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" image: String parent_ids: [ID!] @@ -38,7 +37,7 @@ input TagUpdateInput { aliases: [String!] ignore_auto_tag: Boolean - """This should be a URL or a base64 encoded data URL""" + "This should be a URL or a base64 encoded data URL" image: String parent_ids: [ID!] diff --git a/graphql/stash-box/query.graphql b/graphql/stash-box/query.graphql index cc0017809..502f9e2ac 100644 --- a/graphql/stash-box/query.graphql +++ b/graphql/stash-box/query.graphql @@ -131,7 +131,9 @@ query FindScenesByFullFingerprints($fingerprints: [FingerprintQueryInput!]!) { } } -query FindScenesBySceneFingerprints($fingerprints: [[FingerprintQueryInput!]!]!) { +query FindScenesBySceneFingerprints( + $fingerprints: [[FingerprintQueryInput!]!]! +) { findScenesBySceneFingerprints(fingerprints: $fingerprints) { ...SceneFragment } diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index feddea6e5..60b2d35f4 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -12,14 +12,14 @@ "lint:css": "stylelint --cache \"src/**/*.scss\"", "lint:js": "eslint --cache src/", "check": "tsc --noEmit", - "format": "prettier --write .", - "format-check": "prettier --check .", + "format": "prettier --write . ../../graphql", + "format-check": "prettier --check . ../../graphql", "gqlgen": "gql-gen --config codegen.yml", "extract": "NODE_ENV=development extract-messages -l=en,de -o src/locale -d en --flat false 'src/**/!(*.test).tsx'" }, "dependencies": { "@ant-design/react-slick": "^1.0.0", - "@apollo/client": "^3.7.8", + "@apollo/client": "^3.7.17", "@formatjs/intl-getcanonicallocales": "^2.0.5", "@formatjs/intl-locale": "^3.0.11", "@formatjs/intl-numberformat": "^8.3.3", diff --git a/ui/v2.5/src/components/Tagger/context.tsx b/ui/v2.5/src/components/Tagger/context.tsx index a497f0201..20e924d6a 100644 --- a/ui/v2.5/src/components/Tagger/context.tsx +++ b/ui/v2.5/src/components/Tagger/context.tsx @@ -39,8 +39,12 @@ export interface ITaggerContextState { doSceneFragmentScrape: (sceneID: string) => Promise; doMultiSceneFragmentScrape: (sceneIDs: string[]) => Promise; stopMultiScrape: () => void; - createNewTag: (toCreate: GQL.ScrapedTag) => Promise; + createNewTag: ( + tag: GQL.ScrapedTag, + toCreate: GQL.TagCreateInput + ) => Promise; createNewPerformer: ( + performer: GQL.ScrapedPerformer, toCreate: GQL.PerformerCreateInput ) => Promise; linkPerformer: ( @@ -48,6 +52,7 @@ export interface ITaggerContextState { performerID: string ) => Promise; createNewStudio: ( + studio: GQL.ScrapedStudio, toCreate: GQL.StudioCreateInput ) => Promise; linkStudio: (studio: GQL.ScrapedStudio, studioID: string) => Promise; @@ -484,16 +489,19 @@ export const TaggerContext: React.FC = ({ children }) => { return newSearchResults; } - async function createNewTag(toCreate: GQL.ScrapedTag) { - const tagInput: GQL.TagCreateInput = { name: toCreate.name ?? "" }; + async function createNewTag( + tag: GQL.ScrapedTag, + toCreate: GQL.TagCreateInput + ) { try { const result = await createTag({ variables: { - input: tagInput, + input: toCreate, }, }); const tagID = result.data?.tagCreate?.id; + if (tagID === undefined) return undefined; const newSearchResults = mapResults((r) => { if (!r.tags) { @@ -503,7 +511,7 @@ export const TaggerContext: React.FC = ({ children }) => { return { ...r, tags: r.tags.map((t) => { - if (t.name === toCreate.name) { + if (t.name === tag.name) { return { ...t, stored_id: tagID, @@ -531,7 +539,10 @@ export const TaggerContext: React.FC = ({ children }) => { } } - async function createNewPerformer(toCreate: GQL.PerformerCreateInput) { + async function createNewPerformer( + performer: GQL.ScrapedPerformer, + toCreate: GQL.PerformerCreateInput + ) { try { const result = await createPerformer({ variables: { @@ -540,6 +551,7 @@ export const TaggerContext: React.FC = ({ children }) => { }); const performerID = result.data?.performerCreate?.id; + if (performerID === undefined) return undefined; const newSearchResults = mapResults((r) => { if (!r.performers) { @@ -548,15 +560,15 @@ export const TaggerContext: React.FC = ({ children }) => { return { ...r, - performers: r.performers.map((t) => { - if (t.name === toCreate.name) { + performers: r.performers.map((p) => { + if (p.remote_site_id === performer.remote_site_id) { return { - ...t, + ...p, stored_id: performerID, }; } - return t; + return p; }), }; }); @@ -640,7 +652,12 @@ export const TaggerContext: React.FC = ({ children }) => { } } - async function createNewStudio(toCreate: GQL.StudioCreateInput) { + async function createNewStudio( + studio: GQL.ScrapedStudio, + toCreate: GQL.StudioCreateInput + ) { + if (!currentSource?.stashboxEndpoint) return; + try { const result = await createStudio({ variables: { @@ -649,6 +666,7 @@ export const TaggerContext: React.FC = ({ children }) => { }); const studioID = result.data?.studioCreate?.id; + if (studioID === undefined) return undefined; const newSearchResults = mapResults((r) => { if (!r.studio) { @@ -658,7 +676,7 @@ export const TaggerContext: React.FC = ({ children }) => { return { ...r, studio: - r.studio.name === toCreate.name + r.studio.remote_site_id === studio.remote_site_id ? { ...r.studio, stored_id: studioID, @@ -720,7 +738,7 @@ export const TaggerContext: React.FC = ({ children }) => { return { ...r, studio: - r.remote_site_id === studio.remote_site_id + r.studio.remote_site_id === studio.remote_site_id ? { ...r.studio, stored_id: studioID, diff --git a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx index db117c3e9..927d9c65d 100755 --- a/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx +++ b/ui/v2.5/src/components/Tagger/scenes/StashSearchResult.tsx @@ -396,26 +396,20 @@ const StashSearchResult: React.FC = ({ await saveScene(sceneCreateInput, includeStashID); } - function performerModalCallback( - toCreate?: GQL.PerformerCreateInput | undefined - ) { - if (toCreate) { - createNewPerformer(toCreate); - } - } - function showPerformerModal(t: GQL.ScrapedPerformer) { - createPerformerModal(t, performerModalCallback); - } - - function studioModalCallback(toCreate?: GQL.StudioCreateInput | undefined) { - if (toCreate) { - createNewStudio(toCreate); - } + createPerformerModal(t, (toCreate) => { + if (toCreate) { + createNewPerformer(t, toCreate); + } + }); } function showStudioModal(t: GQL.ScrapedStudio) { - createStudioModal(t, studioModalCallback); + createStudioModal(t, (toCreate) => { + if (toCreate) { + createNewStudio(t, toCreate); + } + }); } // constants to get around dot-notation eslint rule @@ -660,7 +654,8 @@ const StashSearchResult: React.FC = ({ ); async function onCreateTag(t: GQL.ScrapedTag) { - const newTagID = await createNewTag(t); + const toCreate: GQL.TagCreateInput = { name: t.name }; + const newTagID = await createNewTag(t, toCreate); if (newTagID !== undefined) { setTagIDs([...tagIDs, newTagID]); } diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 7e79db3b1..fac247069 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -1,9 +1,9 @@ -import { ApolloCache, DocumentNode } from "@apollo/client"; +import { ApolloCache, DocumentNode, FetchResult } from "@apollo/client"; +import { Modifiers } from "@apollo/client/cache"; import { isField, - resultKeyNameFromField, getQueryDefinition, - getOperationName, + StoreObject, } from "@apollo/client/utilities"; import { stringToGender } from "src/utils/gender"; import { stringToCircumcised } from "src/utils/circumcised"; @@ -17,72 +17,106 @@ const { client } = createClient(); export const getClient = () => client; -const getQueryNames = (queries: DocumentNode[]): string[] => - queries.map((q) => getOperationName(q)).filter((n) => n !== null) as string[]; +// Evicts cached results for the given queries. +// Will also call a cache GC afterwards. +function evictQueries(cache: ApolloCache, queries: DocumentNode[]) { + const fields: Modifiers = {}; + for (const query of queries) { + const { selections } = getQueryDefinition(query).selectionSet; + for (const field of selections) { + if (!isField(field)) continue; + const keyName = field.name.value; + fields[keyName] = (_value, { DELETE }) => DELETE; + } + } -// Will delete the entire cache for any queries passed in -const deleteCache = (queries: DocumentNode[]) => { - const fields = queries - .map((q) => { - const field = getQueryDefinition(q).selectionSet.selections[0]; - return isField(field) ? resultKeyNameFromField(field) : ""; - }) - .filter((name) => name !== "") - .reduce( - (prevFields, name) => ({ - ...prevFields, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [name]: (_items: any, { DELETE }: any) => DELETE, - }), - {} - ); + cache.modify({ fields }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (cache: ApolloCache) => - cache.modify({ - id: "ROOT_QUERY", - fields, - }); + // evictQueries is usually called at the end of + // an update function - so call a GC here + cache.gc(); +} + +/** + * Evicts fields from all objects of a given type. + * + * @param input a map from typename -> list of field names to evict + * @param ignore optionally specify a cache id to ignore and not modify + */ +function evictTypeFields( + cache: ApolloCache>, + input: Record, + ignore?: string +) { + const data = cache.extract(); + for (const key in data) { + if (ignore?.includes(key)) continue; + + const obj = data[key]; + const typename = obj.__typename; + + if (typename && input[typename]) { + const modifiers: Modifiers = {}; + for (const field of input[typename]) { + modifiers[field] = (_value, { DELETE }) => DELETE; + } + cache.modify({ + id: key, + fields: modifiers, + }); + } + } +} + +// Appends obj to the cached result of the given query. +// Use to append objects to "All*" queries in "Create" mutations. +function appendObject( + cache: ApolloCache, + obj: StoreObject, + query: DocumentNode +) { + const field = getQueryDefinition(query).selectionSet.selections[0]; + if (!isField(field)) return; + const keyName = field.name.value; + + cache.modify({ + fields: { + [keyName]: (value, { toReference }) => { + return [...value, toReference(obj)]; + }, + }, + }); +} + +// Deletes obj from the cache, and sets the +// cached result of the given query to null. +// Use with "Destroy" mutations. +function deleteObject( + cache: ApolloCache, + obj: StoreObject, + query: DocumentNode +) { + const field = getQueryDefinition(query).selectionSet.selections[0]; + if (!isField(field)) return; + const keyName = field.name.value; + + cache.writeQuery({ + query, + variables: { id: obj.id }, + data: { [keyName]: null }, + }); + cache.evict({ id: cache.identify(obj) }); +} + +/// Object queries + +export const useFindScene = (id: string) => { + const skip = id === "new"; + return GQL.useFindSceneQuery({ variables: { id }, skip }); }; -export const useFindSavedFilter = (id: string) => - GQL.useFindSavedFilterQuery({ - variables: { - id, - }, - }); - -export const useFindSavedFilters = (mode?: GQL.FilterMode) => - GQL.useFindSavedFiltersQuery({ - variables: { - mode, - }, - }); - -export const useFindDefaultFilter = (mode: GQL.FilterMode) => - GQL.useFindDefaultFilterQuery({ - variables: { - mode, - }, - }); - -export const useFindGalleries = (filter?: ListFilterModel) => - GQL.useFindGalleriesQuery({ - skip: filter === undefined, - variables: { - filter: filter?.makeFindFilter(), - gallery_filter: filter?.makeFilter(), - }, - }); - -export const queryFindGalleries = (filter: ListFilterModel) => - client.query({ - query: GQL.FindGalleriesDocument, - variables: { - filter: filter.makeFindFilter(), - gallery_filter: filter.makeFilter(), - }, - }); +export const useSceneStreams = (id: string) => + GQL.useSceneStreamsQuery({ variables: { id } }); export const useFindScenes = (filter?: ListFilterModel) => GQL.useFindScenesQuery({ @@ -110,6 +144,58 @@ export const queryFindScenesByID = (sceneIDs: number[]) => }, }); +export const querySceneByPathRegex = (filter: GQL.FindFilterType) => + client.query({ + query: GQL.FindScenesByPathRegexDocument, + variables: { filter }, + }); + +export const useFindImage = (id: string) => + GQL.useFindImageQuery({ variables: { id } }); + +export const useFindImages = (filter?: ListFilterModel) => + GQL.useFindImagesQuery({ + skip: filter === undefined, + variables: { + filter: filter?.makeFindFilter(), + image_filter: filter?.makeFilter(), + }, + }); + +export const queryFindImages = (filter: ListFilterModel) => + client.query({ + query: GQL.FindImagesDocument, + variables: { + filter: filter.makeFindFilter(), + image_filter: filter.makeFilter(), + }, + }); + +export const useFindMovie = (id: string) => { + const skip = id === "new"; + return GQL.useFindMovieQuery({ variables: { id }, skip }); +}; + +export const useFindMovies = (filter?: ListFilterModel) => + GQL.useFindMoviesQuery({ + skip: filter === undefined, + variables: { + filter: filter?.makeFindFilter(), + movie_filter: filter?.makeFilter(), + }, + }); + +export const queryFindMovies = (filter: ListFilterModel) => + client.query({ + query: GQL.FindMoviesDocument, + variables: { + filter: filter.makeFindFilter(), + movie_filter: filter.makeFilter(), + }, + }); + +export const useAllMoviesForFilter = () => GQL.useAllMoviesForFilterQuery(); + export const useFindSceneMarkers = (filter?: ListFilterModel) => GQL.useFindSceneMarkersQuery({ skip: filter === undefined, @@ -128,24 +214,74 @@ export const queryFindSceneMarkers = (filter: ListFilterModel) => }, }); -export const useFindImages = (filter?: ListFilterModel) => - GQL.useFindImagesQuery({ +export const useMarkerStrings = () => GQL.useMarkerStringsQuery(); + +export const useFindGallery = (id: string) => { + const skip = id === "new"; + return GQL.useFindGalleryQuery({ variables: { id }, skip }); +}; + +export const useFindGalleries = (filter?: ListFilterModel) => + GQL.useFindGalleriesQuery({ skip: filter === undefined, variables: { filter: filter?.makeFindFilter(), - image_filter: filter?.makeFilter(), + gallery_filter: filter?.makeFilter(), }, }); -export const queryFindImages = (filter: ListFilterModel) => - client.query({ - query: GQL.FindImagesDocument, +export const queryFindGalleries = (filter: ListFilterModel) => + client.query({ + query: GQL.FindGalleriesDocument, variables: { filter: filter.makeFindFilter(), - image_filter: filter.makeFilter(), + gallery_filter: filter.makeFilter(), }, }); +export const useFindPerformer = (id: string) => { + const skip = id === "new"; + return GQL.useFindPerformerQuery({ variables: { id }, skip }); +}; + +export const queryFindPerformer = (id: string) => + client.query({ + query: GQL.FindPerformerDocument, + variables: { id }, + }); + +export const useFindPerformers = (filter?: ListFilterModel) => + GQL.useFindPerformersQuery({ + skip: filter === undefined, + variables: { + filter: filter?.makeFindFilter(), + performer_filter: filter?.makeFilter(), + }, + }); + +export const queryFindPerformers = (filter: ListFilterModel) => + client.query({ + query: GQL.FindPerformersDocument, + variables: { + filter: filter.makeFindFilter(), + performer_filter: filter.makeFilter(), + }, + }); + +export const useAllPerformersForFilter = () => + GQL.useAllPerformersForFilterQuery(); + +export const useFindStudio = (id: string) => { + const skip = id === "new"; + return GQL.useFindStudioQuery({ variables: { id }, skip }); +}; + +export const queryFindStudio = (id: string) => + client.query({ + query: GQL.FindStudioDocument, + variables: { id }, + }); + export const useFindStudios = (filter?: ListFilterModel) => GQL.useFindStudiosQuery({ skip: filter === undefined, @@ -164,32 +300,12 @@ export const queryFindStudios = (filter: ListFilterModel) => }, }); -export const useFindMovies = (filter?: ListFilterModel) => - GQL.useFindMoviesQuery({ - skip: filter === undefined, - variables: { - filter: filter?.makeFindFilter(), - movie_filter: filter?.makeFilter(), - }, - }); +export const useAllStudiosForFilter = () => GQL.useAllStudiosForFilterQuery(); -export const queryFindMovies = (filter: ListFilterModel) => - client.query({ - query: GQL.FindMoviesDocument, - variables: { - filter: filter.makeFindFilter(), - movie_filter: filter.makeFilter(), - }, - }); - -export const useFindPerformers = (filter?: ListFilterModel) => - GQL.useFindPerformersQuery({ - skip: filter === undefined, - variables: { - filter: filter?.makeFindFilter(), - performer_filter: filter?.makeFilter(), - }, - }); +export const useFindTag = (id: string) => { + const skip = id === "new"; + return GQL.useFindTagQuery({ variables: { id }, skip }); +}; export const useFindTags = (filter?: ListFilterModel) => GQL.useFindTagsQuery({ @@ -209,290 +325,313 @@ export const queryFindTags = (filter: ListFilterModel) => }, }); -export const queryFindPerformers = (filter: ListFilterModel) => - client.query({ - query: GQL.FindPerformersDocument, - variables: { - filter: filter.makeFindFilter(), - performer_filter: filter.makeFilter(), - }, - }); - -export const useFindGallery = (id: string) => { - const skip = id === "new"; - return GQL.useFindGalleryQuery({ variables: { id }, skip }); -}; -export const useFindScene = (id: string) => { - const skip = id === "new"; - return GQL.useFindSceneQuery({ variables: { id }, skip }); -}; -export const useSceneStreams = (id: string) => - GQL.useSceneStreamsQuery({ variables: { id } }); - -export const useFindImage = (id: string) => - GQL.useFindImageQuery({ variables: { id } }); - -export const queryFindPerformer = (id: string) => - client.query({ - query: GQL.FindPerformerDocument, - variables: { - id, - }, - }); - -export const useFindPerformer = (id: string) => { - const skip = id === "new"; - return GQL.useFindPerformerQuery({ variables: { id }, skip }); -}; -export const useFindStudio = (id: string) => { - const skip = id === "new"; - return GQL.useFindStudioQuery({ variables: { id }, skip }); -}; -export const queryFindStudio = (id: string) => - client.query({ - query: GQL.FindStudioDocument, - variables: { - id, - }, - }); -export const useFindMovie = (id: string) => { - const skip = id === "new"; - return GQL.useFindMovieQuery({ variables: { id }, skip }); -}; -export const useFindTag = (id: string) => { - const skip = id === "new"; - return GQL.useFindTagQuery({ variables: { id }, skip }); -}; - -const sceneMarkerMutationImpactedQueries = [ - GQL.FindSceneDocument, - GQL.FindScenesDocument, - GQL.FindSceneMarkersDocument, - GQL.MarkerStringsDocument, - GQL.FindSceneMarkerTagsDocument, -]; - -export const useSceneMarkerCreate = () => - GQL.useSceneMarkerCreateMutation({ - refetchQueries: getQueryNames([GQL.FindSceneDocument]), - update: deleteCache(sceneMarkerMutationImpactedQueries), - }); -export const useSceneMarkerUpdate = () => - GQL.useSceneMarkerUpdateMutation({ - refetchQueries: getQueryNames([GQL.FindSceneDocument]), - update: deleteCache(sceneMarkerMutationImpactedQueries), - }); -export const useSceneMarkerDestroy = () => - GQL.useSceneMarkerDestroyMutation({ - refetchQueries: getQueryNames([GQL.FindSceneDocument]), - update: deleteCache(sceneMarkerMutationImpactedQueries), - }); - -export const useListPerformerScrapers = () => - GQL.useListPerformerScrapersQuery(); -export const useScrapePerformerList = (scraperId: string, q: string) => - GQL.useScrapeSinglePerformerQuery({ - variables: { - source: { - scraper_id: scraperId, - }, - input: { - query: q, - }, - }, - skip: q === "", - }); - -export const useListSceneScrapers = () => GQL.useListSceneScrapersQuery(); - -export const useListGalleryScrapers = () => GQL.useListGalleryScrapersQuery(); - -export const useListMovieScrapers = () => GQL.useListMovieScrapersQuery(); - -export const useScrapeFreeonesPerformers = (q: string) => - GQL.useScrapeFreeonesPerformersQuery({ variables: { q } }); - -export const usePlugins = () => GQL.usePluginsQuery(); -export const usePluginTasks = () => GQL.usePluginTasksQuery(); - -export const useMarkerStrings = () => GQL.useMarkerStringsQuery(); export const useAllTagsForFilter = () => GQL.useAllTagsForFilterQuery(); -export const useAllPerformersForFilter = () => - GQL.useAllPerformersForFilterQuery(); -export const useAllStudiosForFilter = () => GQL.useAllStudiosForFilterQuery(); -export const useAllMoviesForFilter = () => GQL.useAllMoviesForFilterQuery(); -export const useStats = () => GQL.useStatsQuery(); -export const useVersion = () => GQL.useVersionQuery(); -export const useLatestVersion = () => - GQL.useLatestVersionQuery({ - notifyOnNetworkStatusChange: true, - errorPolicy: "ignore", + +export const useFindSavedFilter = (id: string) => + GQL.useFindSavedFilterQuery({ + variables: { id }, }); -export const useConfiguration = () => GQL.useConfigurationQuery(); -export const mutateSetup = (input: GQL.SetupInput) => - client.mutate({ - mutation: GQL.SetupDocument, - variables: { input }, - refetchQueries: getQueryNames([ - GQL.ConfigurationDocument, - GQL.SystemStatusDocument, - ]), - update: deleteCache([GQL.ConfigurationDocument, GQL.SystemStatusDocument]), +export const useFindSavedFilters = (mode?: GQL.FilterMode) => + GQL.useFindSavedFiltersQuery({ + variables: { mode }, }); -export const mutateMigrate = (input: GQL.MigrateInput) => - client.mutate({ - mutation: GQL.MigrateDocument, - variables: { input }, - refetchQueries: getQueryNames([ - GQL.ConfigurationDocument, - GQL.SystemStatusDocument, - ]), - update: deleteCache([GQL.ConfigurationDocument, GQL.SystemStatusDocument]), +export const useFindDefaultFilter = (mode: GQL.FilterMode) => + GQL.useFindDefaultFilterQuery({ + variables: { mode }, }); -export const useDirectory = (path?: string) => - GQL.useDirectoryQuery({ variables: { path } }); - -const performerMutationImpactedQueries = [ - GQL.FindPerformersDocument, - GQL.FindSceneDocument, - GQL.FindScenesDocument, - GQL.AllPerformersForFilterDocument, -]; - -export const usePerformerCreate = () => - GQL.usePerformerCreateMutation({ - refetchQueries: getQueryNames([ - GQL.FindPerformersDocument, - GQL.AllPerformersForFilterDocument, - ]), - update: deleteCache([ - GQL.FindPerformersDocument, - GQL.AllPerformersForFilterDocument, - ]), - }); -export const usePerformerUpdate = () => - GQL.usePerformerUpdateMutation({ - update: deleteCache(performerMutationImpactedQueries), - }); - -export const useBulkPerformerUpdate = (input: GQL.BulkPerformerUpdateInput) => - GQL.useBulkPerformerUpdateMutation({ - variables: { - input, - }, - update: deleteCache(performerMutationImpactedQueries), - }); - -export const usePerformerDestroy = () => - GQL.usePerformerDestroyMutation({ - refetchQueries: getQueryNames([ - GQL.FindPerformersDocument, - GQL.AllPerformersForFilterDocument, - ]), - update: deleteCache(performerMutationImpactedQueries), - }); - -export const usePerformersDestroy = ( - variables: GQL.PerformersDestroyMutationVariables -) => - GQL.usePerformersDestroyMutation({ - variables, - refetchQueries: getQueryNames([ - GQL.FindPerformersDocument, - GQL.AllPerformersForFilterDocument, - ]), - update: deleteCache(performerMutationImpactedQueries), - }); - -const sceneMutationImpactedQueries = [ - GQL.FindPerformerDocument, - GQL.FindPerformersDocument, - GQL.FindScenesDocument, - GQL.FindSceneMarkersDocument, - GQL.FindStudioDocument, - GQL.FindStudiosDocument, - GQL.FindMovieDocument, - GQL.FindMoviesDocument, - GQL.FindTagDocument, - GQL.FindTagsDocument, -]; - -export const useSceneUpdate = () => - GQL.useSceneUpdateMutation({ - update: deleteCache(sceneMutationImpactedQueries), - }); - -export const useBulkSceneUpdate = (input: GQL.BulkSceneUpdateInput) => - GQL.useBulkSceneUpdateMutation({ - variables: { - input, - }, - update: deleteCache(sceneMutationImpactedQueries), - }); - -export const useScenesUpdate = (input: GQL.SceneUpdateInput[]) => - GQL.useScenesUpdateMutation({ variables: { input } }); - -type SceneOMutation = - | GQL.SceneIncrementOMutation - | GQL.SceneDecrementOMutation - | GQL.SceneResetOMutation; -const updateSceneO = ( - id: string, - cache: ApolloCache, - updatedOCount?: number -) => { - if (updatedOCount === undefined) return; +/// Object Mutations +// Increases/decreases the given field of the Stats query by diff +function updateStats(cache: ApolloCache, field: string, diff: number) { cache.modify({ - id: cache.identify({ __typename: "Scene", id }), + fields: { + stats(value) { + return { + ...value, + [field]: value[field] + diff, + }; + }, + }, + }); +} + +function updateO( + cache: ApolloCache, + typename: string, + id: string, + updatedOCount: number +) { + cache.modify({ + id: cache.identify({ __typename: typename, id }), fields: { o_counter() { return updatedOCount; }, }, }); +} + +const sceneMutationImpactedTypeFields = { + Movie: ["scenes", "scene_count"], + Gallery: ["scenes"], + Performer: [ + "scenes", + "scene_count", + "movies", + "movie_count", + "performer_count", + ], + Studio: ["scene_count", "performer_count"], + Tag: ["scene_count"], }; -export const useSceneIncrementO = (id: string) => - GQL.useSceneIncrementOMutation({ - variables: { id }, - update: (cache, data) => - updateSceneO(id, cache, data.data?.sceneIncrementO), +const sceneMutationImpactedQueries = [ + GQL.FindScenesDocument, // various filters + GQL.FindMoviesDocument, // is missing scenes + GQL.FindGalleriesDocument, // is missing scenes + GQL.FindPerformersDocument, // filter by scene count + GQL.FindStudiosDocument, // filter by scene count + GQL.FindTagsDocument, // filter by scene count +]; + +export const mutateCreateScene = (input: GQL.SceneCreateInput) => + client.mutate({ + mutation: GQL.SceneCreateDocument, + variables: { input }, + update(cache, result) { + const scene = result.data?.sceneCreate; + if (!scene) return; + + // update stats + updateStats(cache, "scene_count", 1); + + // if we're reassigning files, refetch files from other scenes + if (input.file_ids?.length) { + const obj = { __typename: "Scene", id: scene.id }; + evictTypeFields( + cache, + { + ...sceneMutationImpactedTypeFields, + Scene: ["files"], + }, + cache.identify(obj) // don't evict this scene + ); + } else { + evictTypeFields(cache, sceneMutationImpactedTypeFields); + } + + evictQueries(cache, sceneMutationImpactedQueries); + }, }); -export const useSceneDecrementO = (id: string) => - GQL.useSceneDecrementOMutation({ - variables: { id }, - update: (cache, data) => - updateSceneO(id, cache, data.data?.sceneDecrementO), +export const useSceneUpdate = () => + GQL.useSceneUpdateMutation({ + update(cache, result) { + if (!result.data?.sceneUpdate) return; + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, sceneMutationImpactedQueries); + }, }); -export const useSceneResetO = (id: string) => - GQL.useSceneResetOMutation({ - variables: { id }, - update: (cache, data) => updateSceneO(id, cache, data.data?.sceneResetO), +export const useBulkSceneUpdate = (input: GQL.BulkSceneUpdateInput) => + GQL.useBulkSceneUpdateMutation({ + variables: { input }, + update(cache, result) { + if (!result.data?.bulkSceneUpdate) return; + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, sceneMutationImpactedQueries); + }, + }); + +export const useScenesUpdate = (input: GQL.SceneUpdateInput[]) => + GQL.useScenesUpdateMutation({ + variables: { input }, + update(cache, result) { + if (!result.data?.scenesUpdate) return; + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, sceneMutationImpactedQueries); + }, }); export const useSceneDestroy = (input: GQL.SceneDestroyInput) => GQL.useSceneDestroyMutation({ variables: input, - update: deleteCache(sceneMutationImpactedQueries), + update(cache, result) { + if (!result.data?.sceneDestroy) return; + + const obj = { __typename: "Scene", id: input.id }; + deleteObject(cache, obj, GQL.FindSceneDocument); + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, [ + ...sceneMutationImpactedQueries, + GQL.FindSceneMarkersDocument, // filter by scene tags + GQL.StatsDocument, // scenes size, scene count, etc + ]); + }, }); export const useScenesDestroy = (input: GQL.ScenesDestroyInput) => GQL.useScenesDestroyMutation({ variables: input, - update: deleteCache(sceneMutationImpactedQueries), + update(cache, result) { + if (!result.data?.scenesDestroy) return; + + for (const id of input.ids) { + const obj = { __typename: "Scene", id }; + deleteObject(cache, obj, GQL.FindSceneDocument); + } + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, [ + ...sceneMutationImpactedQueries, + GQL.FindSceneMarkersDocument, // filter by scene tags + GQL.StatsDocument, // scenes size, scene count, etc + ]); + }, + }); + +export const useSceneIncrementO = (id: string) => + GQL.useSceneIncrementOMutation({ + variables: { id }, + update(cache, result) { + const updatedOCount = result.data?.sceneIncrementO; + if (updatedOCount === undefined) return; + + const scene = cache.readFragment({ + id: cache.identify({ __typename: "Scene", id }), + fragment: GQL.SlimSceneDataFragmentDoc, + fragmentName: "SlimSceneData", + }); + + if (scene) { + // if we have the scene, update performer o_counters manually + for (const performer of scene.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value + 1; + }, + }, + }); + } + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + } + + updateStats(cache, "total_o_count", 1); + updateO(cache, "Scene", id, updatedOCount); + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }, + }); + +export const useSceneDecrementO = (id: string) => + GQL.useSceneDecrementOMutation({ + variables: { id }, + update(cache, result) { + const updatedOCount = result.data?.sceneDecrementO; + if (updatedOCount === undefined) return; + + const scene = cache.readFragment({ + id: cache.identify({ __typename: "Scene", id }), + fragment: GQL.SlimSceneDataFragmentDoc, + fragmentName: "SlimSceneData", + }); + + if (scene) { + // if we have the scene, update performer o_counters manually + for (const performer of scene.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value - 1; + }, + }, + }); + } + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + } + + updateStats(cache, "total_o_count", -1); + updateO(cache, "Scene", id, updatedOCount); + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }, + }); + +export const useSceneResetO = (id: string) => + GQL.useSceneResetOMutation({ + variables: { id }, + update(cache, result) { + const updatedOCount = result.data?.sceneResetO; + if (updatedOCount === undefined) return; + + const scene = cache.readFragment({ + id: cache.identify({ __typename: "Scene", id }), + fragment: GQL.SlimSceneDataFragmentDoc, + fragmentName: "SlimSceneData", + }); + + if (scene) { + // if we have the scene, update performer o_counters manually + const old_count = scene.o_counter ?? 0; + for (const performer of scene.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value - old_count; + }, + }, + }); + } + updateStats(cache, "total_o_count", -old_count); + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + // also refresh stats total_o_count + cache.modify({ + fields: { + stats: (value) => ({ + ...value, + total_o_count: undefined, + }), + }, + }); + } + + updateO(cache, "Scene", id, updatedOCount); + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }, }); export const useSceneGenerateScreenshot = () => - GQL.useSceneGenerateScreenshotMutation({ - update: deleteCache([GQL.FindScenesDocument]), - }); + GQL.useSceneGenerateScreenshotMutation(); export const mutateSceneSetPrimaryFile = (id: string, fileID: string) => client.mutate({ @@ -503,7 +642,13 @@ export const mutateSceneSetPrimaryFile = (id: string, fileID: string) => primary_file_id: fileID, }, }, - update: deleteCache(sceneMutationImpactedQueries), + update(cache, result) { + if (!result.data?.sceneUpdate) return; + + evictQueries(cache, [ + GQL.FindScenesDocument, // sort by primary basename when missing title + ]); + }, }); export const mutateSceneAssignFile = (sceneID: string, fileID: string) => @@ -515,11 +660,21 @@ export const mutateSceneAssignFile = (sceneID: string, fileID: string) => file_id: fileID, }, }, - update: deleteCache([ - ...sceneMutationImpactedQueries, - GQL.FindSceneDocument, - ]), - refetchQueries: getQueryNames([GQL.FindSceneDocument]), + update(cache, result) { + if (!result.data?.sceneAssignFile) return; + + // refetch target scene + cache.evict({ + id: cache.identify({ __typename: "Scene", id: sceneID }), + }); + + // refetch files of the scene the file was previously assigned to + evictTypeFields(cache, { Scene: ["files"] }); + + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by file count + ]); + }, }); export const mutateSceneMerge = ( @@ -536,152 +691,312 @@ export const mutateSceneMerge = ( values, }, }, - update: (cache) => { - // evict the merged scenes from the cache so that they are reloaded - cache.evict({ - id: cache.identify({ __typename: "Scene", id: destination }), + update(cache, result) { + if (!result.data?.sceneMerge) return; + + for (const id of source) { + const obj = { __typename: "Scene", id }; + deleteObject(cache, obj, GQL.FindSceneDocument); + } + + evictTypeFields(cache, sceneMutationImpactedTypeFields); + evictQueries(cache, [ + ...sceneMutationImpactedQueries, + GQL.StatsDocument, // scenes size, scene count, etc + ]); + }, + }); + +export const useSceneSaveActivity = () => + GQL.useSceneSaveActivityMutation({ + update(cache, result, { variables }) { + if (!result.data?.sceneSaveActivity || !variables) return; + + const { id, playDuration, resume_time: resumeTime } = variables; + + cache.modify({ + id: cache.identify({ __typename: "Scene", id }), + fields: { + resume_time() { + return resumeTime; + }, + play_duration(value) { + return value + playDuration; + }, + }, }); - source.forEach((id) => - cache.evict({ id: cache.identify({ __typename: "Scene", id }) }) - ); - cache.gc(); - deleteCache([...sceneMutationImpactedQueries, GQL.FindSceneDocument])( - cache - ); + if (playDuration) { + updateStats(cache, "total_play_duration", playDuration); + } + + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by play duration + ]); }, - refetchQueries: getQueryNames([GQL.FindSceneDocument]), }); -export const mutateCreateScene = (input: GQL.SceneCreateInput) => - client.mutate({ - mutation: GQL.SceneCreateDocument, - variables: { - input, +export const useSceneIncrementPlayCount = () => + GQL.useSceneIncrementPlayCountMutation({ + update(cache, result, { variables }) { + if (!result.data?.sceneIncrementPlayCount || !variables) return; + + let lastPlayCount = 0; + cache.modify({ + id: cache.identify({ __typename: "Scene", id: variables.id }), + fields: { + play_count(value) { + lastPlayCount = value; + return value + 1; + }, + last_played_at() { + // this is not perfectly accurate, the time is set server-side + // it isn't even displayed anywhere in the UI anyway + return new Date().toISOString(); + }, + }, + }); + + updateStats(cache, "total_play_count", 1); + if (lastPlayCount === 0) { + updateStats(cache, "scenes_played", 1); + } + + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by play count + ]); }, - update: deleteCache(sceneMutationImpactedQueries), - refetchQueries: getQueryNames([GQL.FindSceneDocument]), }); +const imageMutationImpactedTypeFields = { + Gallery: ["images", "image_count"], + Performer: ["image_count", "performer_count"], + Studio: ["image_count", "performer_count"], + Tag: ["image_count"], +}; + const imageMutationImpactedQueries = [ - GQL.FindPerformerDocument, - GQL.FindPerformersDocument, - GQL.FindImagesDocument, - GQL.FindStudioDocument, - GQL.FindStudiosDocument, - GQL.FindTagDocument, - GQL.FindTagsDocument, - GQL.FindGalleryDocument, - GQL.FindGalleriesDocument, + GQL.FindImagesDocument, // various filters + GQL.FindGalleriesDocument, // filter by image count + GQL.FindPerformersDocument, // filter by image count + GQL.FindStudiosDocument, // filter by image count + GQL.FindTagsDocument, // filter by image count ]; export const useImageUpdate = () => GQL.useImageUpdateMutation({ - update: deleteCache(imageMutationImpactedQueries), + update(cache, result) { + if (!result.data?.imageUpdate) return; + + evictTypeFields(cache, imageMutationImpactedTypeFields); + evictQueries(cache, imageMutationImpactedQueries); + }, }); export const useBulkImageUpdate = () => GQL.useBulkImageUpdateMutation({ - update: deleteCache(imageMutationImpactedQueries), + update(cache, result) { + if (!result.data?.bulkImageUpdate) return; + + evictTypeFields(cache, imageMutationImpactedTypeFields); + evictQueries(cache, imageMutationImpactedQueries); + }, }); export const useImagesDestroy = (input: GQL.ImagesDestroyInput) => GQL.useImagesDestroyMutation({ variables: input, - update: deleteCache(imageMutationImpactedQueries), - }); + update(cache, result) { + if (!result.data?.imagesDestroy) return; -type ImageOMutation = - | GQL.ImageIncrementOMutation - | GQL.ImageDecrementOMutation - | GQL.ImageResetOMutation; -const updateImageO = ( - id: string, - cache: ApolloCache, - updatedOCount?: number -) => { - const image = cache.readQuery< - GQL.FindImageQuery, - GQL.FindImageQueryVariables - >({ - query: GQL.FindImageDocument, - variables: { id }, - }); - if (updatedOCount === undefined || !image?.findImage) return; + for (const id of input.ids) { + const obj = { __typename: "Image", id }; + deleteObject(cache, obj, GQL.FindImageDocument); + } - cache.writeQuery({ - query: GQL.FindImageDocument, - variables: { id }, - data: { - findImage: { - ...image.findImage, - o_counter: updatedOCount, - }, + evictTypeFields(cache, imageMutationImpactedTypeFields); + evictQueries(cache, [ + ...imageMutationImpactedQueries, + GQL.StatsDocument, // images size, images count + ]); }, }); -}; +function updateImageIncrementO(id: string) { + return ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cache: ApolloCache, + result: FetchResult + ) => { + const updatedOCount = result.data?.imageIncrementO; + if (updatedOCount === undefined) return; + + const image = cache.readFragment({ + id: cache.identify({ __typename: "Image", id }), + fragment: GQL.SlimImageDataFragmentDoc, + fragmentName: "SlimImageData", + }); + + if (image) { + // if we have the image, update performer o_counters manually + for (const performer of image.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value + 1; + }, + }, + }); + } + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + } + + updateStats(cache, "total_o_count", 1); + updateO(cache, "Image", id, updatedOCount); + evictQueries(cache, [ + GQL.FindImagesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }; +} export const useImageIncrementO = (id: string) => GQL.useImageIncrementOMutation({ variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageIncrementO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageIncrementO(id), }); export const mutateImageIncrementO = (id: string) => client.mutate({ mutation: GQL.ImageIncrementODocument, variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageIncrementO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageIncrementO(id), }); +function updateImageDecrementO(id: string) { + return ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cache: ApolloCache, + result: FetchResult + ) => { + const updatedOCount = result.data?.imageDecrementO; + if (updatedOCount === undefined) return; + + const image = cache.readFragment({ + id: cache.identify({ __typename: "Image", id }), + fragment: GQL.SlimImageDataFragmentDoc, + fragmentName: "SlimImageData", + }); + + if (image) { + // if we have the image, update performer o_counters manually + for (const performer of image.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value - 1; + }, + }, + }); + } + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + } + + updateStats(cache, "total_o_count", -1); + updateO(cache, "Image", id, updatedOCount); + evictQueries(cache, [ + GQL.FindImagesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }; +} + export const useImageDecrementO = (id: string) => GQL.useImageDecrementOMutation({ variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageDecrementO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageDecrementO(id), }); export const mutateImageDecrementO = (id: string) => client.mutate({ mutation: GQL.ImageDecrementODocument, variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageDecrementO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageDecrementO(id), }); +function updateImageResetO(id: string) { + return ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cache: ApolloCache, + result: FetchResult + ) => { + const updatedOCount = result.data?.imageResetO; + if (updatedOCount === undefined) return; + + const image = cache.readFragment({ + id: cache.identify({ __typename: "Image", id }), + fragment: GQL.SlimImageDataFragmentDoc, + fragmentName: "SlimImageData", + }); + + if (image) { + // if we have the image, update performer o_counters manually + const old_count = image.o_counter ?? 0; + for (const performer of image.performers) { + cache.modify({ + id: cache.identify(performer), + fields: { + o_counter(value) { + return value - old_count; + }, + }, + }); + } + updateStats(cache, "total_o_count", -old_count); + } else { + // else refresh all performer o_counters + evictTypeFields(cache, { + Performer: ["o_counter"], + }); + // also refresh stats total_o_count + cache.modify({ + fields: { + stats: (value) => ({ + ...value, + total_o_count: undefined, + }), + }, + }); + } + + updateO(cache, "Image", id, updatedOCount); + evictQueries(cache, [ + GQL.FindImagesDocument, // filter by o_counter + GQL.FindPerformersDocument, // filter by o_counter + ]); + }; +} + export const useImageResetO = (id: string) => GQL.useImageResetOMutation({ variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageResetO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageResetO(id), }); export const mutateImageResetO = (id: string) => client.mutate({ mutation: GQL.ImageResetODocument, variables: { id }, - update: (cache, data) => { - updateImageO(id, cache, data.data?.imageResetO); - // impacts FindImages as well as FindImage - deleteCache([GQL.FindImagesDocument])(cache); - }, + update: updateImageResetO(id), }); export const mutateImageSetPrimaryFile = (id: string, fileID: string) => @@ -693,56 +1008,291 @@ export const mutateImageSetPrimaryFile = (id: string, fileID: string) => primary_file_id: fileID, }, }, - update: deleteCache(imageMutationImpactedQueries), + update(cache, result) { + if (!result.data?.imageUpdate) return; + + evictQueries(cache, [ + GQL.FindImagesDocument, // sort by primary basename when missing title + ]); + }, }); +const movieMutationImpactedTypeFields = { + Studio: ["movie_count"], +}; + +const movieMutationImpactedQueries = [ + GQL.FindMoviesDocument, // various filters +]; + +export const useMovieCreate = () => + GQL.useMovieCreateMutation({ + update(cache, result) { + const movie = result.data?.movieCreate; + if (!movie) return; + + appendObject(cache, movie, GQL.AllMoviesForFilterDocument); + + // update stats + updateStats(cache, "studio_count", 1); + + evictTypeFields(cache, movieMutationImpactedTypeFields); + evictQueries(cache, movieMutationImpactedQueries); + }, + }); + +export const useMovieUpdate = () => + GQL.useMovieUpdateMutation({ + update(cache, result) { + if (!result.data?.movieUpdate) return; + + evictTypeFields(cache, movieMutationImpactedTypeFields); + evictQueries(cache, movieMutationImpactedQueries); + }, + }); + +export const useBulkMovieUpdate = (input: GQL.BulkMovieUpdateInput) => + GQL.useBulkMovieUpdateMutation({ + variables: { input }, + update(cache, result) { + if (!result.data?.bulkMovieUpdate) return; + + evictTypeFields(cache, movieMutationImpactedTypeFields); + evictQueries(cache, movieMutationImpactedQueries); + }, + }); + +export const useMovieDestroy = (input: GQL.MovieDestroyInput) => + GQL.useMovieDestroyMutation({ + variables: input, + update(cache, result) { + if (!result.data?.movieDestroy) return; + + const obj = { __typename: "Movie", id: input.id }; + deleteObject(cache, obj, GQL.FindMovieDocument); + + // update stats + updateStats(cache, "movie_count", -1); + + evictTypeFields(cache, { + Scene: ["movies"], + Performer: ["movie_count"], + Studio: ["movie_count"], + }); + evictQueries(cache, [ + ...movieMutationImpactedQueries, + GQL.FindScenesDocument, // filter by movie + ]); + }, + }); + +export const useMoviesDestroy = (input: GQL.MoviesDestroyMutationVariables) => + GQL.useMoviesDestroyMutation({ + variables: input, + update(cache, result) { + if (!result.data?.moviesDestroy) return; + + const { ids } = input; + + for (const id of ids) { + const obj = { __typename: "Movie", id }; + deleteObject(cache, obj, GQL.FindMovieDocument); + } + + // update stats + updateStats(cache, "movie_count", -ids.length); + + evictTypeFields(cache, { + Scene: ["movies"], + Performer: ["movie_count"], + Studio: ["movie_count"], + }); + evictQueries(cache, [ + ...movieMutationImpactedQueries, + GQL.FindScenesDocument, // filter by movie + ]); + }, + }); + +const sceneMarkerMutationImpactedTypeFields = { + Tag: ["scene_marker_count"], +}; + +const sceneMarkerMutationImpactedQueries = [ + GQL.FindScenesDocument, // has marker filter + GQL.FindSceneMarkersDocument, // various filters + GQL.MarkerStringsDocument, // marker list + GQL.FindSceneMarkerTagsDocument, // marker tag list + GQL.FindTagsDocument, // filter by marker count +]; + +export const useSceneMarkerCreate = () => + GQL.useSceneMarkerCreateMutation({ + update(cache, result, { variables }) { + if (!result.data?.sceneMarkerCreate || !variables) return; + + // refetch linked scene's marker list + cache.evict({ + id: cache.identify({ __typename: "Scene", id: variables.scene_id }), + fieldName: "scene_markers", + }); + + evictTypeFields(cache, sceneMarkerMutationImpactedTypeFields); + evictQueries(cache, sceneMarkerMutationImpactedQueries); + }, + }); + +export const useSceneMarkerUpdate = () => + GQL.useSceneMarkerUpdateMutation({ + update(cache, result, { variables }) { + if (!result.data?.sceneMarkerUpdate || !variables) return; + + // refetch linked scene's marker list + cache.evict({ + id: cache.identify({ __typename: "Scene", id: variables.scene_id }), + fieldName: "scene_markers", + }); + + evictTypeFields(cache, sceneMarkerMutationImpactedTypeFields); + evictQueries(cache, sceneMarkerMutationImpactedQueries); + }, + }); + +export const useSceneMarkerDestroy = () => + GQL.useSceneMarkerDestroyMutation({ + update(cache, result, { variables }) { + if (!result.data?.sceneMarkerDestroy || !variables) return; + + const obj = { __typename: "SceneMarker", id: variables.id }; + cache.evict({ id: cache.identify(obj) }); + + evictTypeFields(cache, sceneMarkerMutationImpactedTypeFields); + evictQueries(cache, sceneMarkerMutationImpactedQueries); + }, + }); + +const galleryMutationImpactedTypeFields = { + Scene: ["galleries"], + Performer: ["gallery_count", "performer_count"], + Studio: ["gallery_count", "performer_count"], + Tag: ["gallery_count"], +}; + const galleryMutationImpactedQueries = [ - GQL.FindPerformerDocument, - GQL.FindPerformersDocument, - GQL.FindImagesDocument, - GQL.FindStudioDocument, - GQL.FindStudiosDocument, - GQL.FindTagDocument, - GQL.FindTagsDocument, - GQL.FindGalleryDocument, - GQL.FindGalleriesDocument, + GQL.FindScenesDocument, // is missing galleries + GQL.FindGalleriesDocument, // various filters + GQL.FindPerformersDocument, // filter by gallery count + GQL.FindStudiosDocument, // filter by gallery count + GQL.FindTagsDocument, // filter by gallery count ]; export const useGalleryCreate = () => GQL.useGalleryCreateMutation({ - update: deleteCache(galleryMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryCreate) return; + + // update stats + updateStats(cache, "gallery_count", 1); + + evictTypeFields(cache, galleryMutationImpactedTypeFields); + evictQueries(cache, galleryMutationImpactedQueries); + }, }); export const useGalleryUpdate = () => GQL.useGalleryUpdateMutation({ - update: deleteCache(galleryMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryUpdate) return; + + evictTypeFields(cache, galleryMutationImpactedTypeFields); + evictQueries(cache, galleryMutationImpactedQueries); + }, }); export const useBulkGalleryUpdate = () => GQL.useBulkGalleryUpdateMutation({ - update: deleteCache(galleryMutationImpactedQueries), + update(cache, result) { + if (!result.data?.bulkGalleryUpdate) return; + + evictTypeFields(cache, galleryMutationImpactedTypeFields); + evictQueries(cache, galleryMutationImpactedQueries); + }, }); export const useGalleryDestroy = (input: GQL.GalleryDestroyInput) => GQL.useGalleryDestroyMutation({ variables: input, - update: deleteCache(galleryMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryDestroy) return; + + for (const id of input.ids) { + const obj = { __typename: "Gallery", id }; + deleteObject(cache, obj, GQL.FindGalleryDocument); + } + + evictTypeFields(cache, galleryMutationImpactedTypeFields); + evictQueries(cache, [ + ...galleryMutationImpactedQueries, + GQL.FindImagesDocument, // filter by gallery + GQL.StatsDocument, // images size, gallery count, etc + ]); + }, }); export const mutateAddGalleryImages = (input: GQL.GalleryAddInput) => client.mutate({ mutation: GQL.AddGalleryImagesDocument, variables: input, - update: deleteCache(galleryMutationImpactedQueries), - refetchQueries: getQueryNames([GQL.FindGalleryDocument]), + update(cache, result) { + if (!result.data?.addGalleryImages) return; + + // refetch gallery image_count + cache.evict({ + id: cache.identify({ __typename: "Gallery", id: input.gallery_id }), + fieldName: "image_count", + }); + + // refetch images galleries field + for (const id of input.image_ids) { + cache.evict({ + id: cache.identify({ __typename: "Image", id }), + fieldName: "galleries", + }); + } + + evictQueries(cache, [ + GQL.FindGalleriesDocument, // filter by image count + GQL.FindImagesDocument, // filter by gallery + ]); + }, }); export const mutateRemoveGalleryImages = (input: GQL.GalleryRemoveInput) => client.mutate({ mutation: GQL.RemoveGalleryImagesDocument, variables: input, - update: deleteCache(galleryMutationImpactedQueries), - refetchQueries: getQueryNames([GQL.FindGalleryDocument]), + update(cache, result) { + if (!result.data?.removeGalleryImages) return; + + // refetch gallery image_count + cache.evict({ + id: cache.identify({ __typename: "Gallery", id: input.gallery_id }), + fieldName: "image_count", + }); + + // refetch images galleries field + for (const id of input.image_ids) { + cache.evict({ + id: cache.identify({ __typename: "Image", id }), + fieldName: "galleries", + }); + } + + evictQueries(cache, [ + GQL.FindGalleriesDocument, // filter by image count + GQL.FindImagesDocument, // filter by gallery + ]); + }, }); export const mutateGallerySetPrimaryFile = (id: string, fileID: string) => @@ -754,311 +1304,463 @@ export const mutateGallerySetPrimaryFile = (id: string, fileID: string) => primary_file_id: fileID, }, }, - update: deleteCache(galleryMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryUpdate) return; + + evictQueries(cache, [ + GQL.FindGalleriesDocument, // sort by primary basename when missing title + ]); + }, }); +const galleryChapterMutationImpactedTypeFields = { + Gallery: ["chapters"], +}; + const galleryChapterMutationImpactedQueries = [ - GQL.FindGalleryDocument, - GQL.FindGalleriesDocument, + GQL.FindGalleriesDocument, // filter by has chapters ]; export const useGalleryChapterCreate = () => GQL.useGalleryChapterCreateMutation({ - refetchQueries: getQueryNames([GQL.FindGalleryDocument]), - update: deleteCache(galleryChapterMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryChapterCreate) return; + + evictTypeFields(cache, galleryChapterMutationImpactedTypeFields); + evictQueries(cache, galleryChapterMutationImpactedQueries); + }, }); + export const useGalleryChapterUpdate = () => GQL.useGalleryChapterUpdateMutation({ - refetchQueries: getQueryNames([GQL.FindGalleryDocument]), - update: deleteCache(galleryChapterMutationImpactedQueries), + update(cache, result) { + if (!result.data?.galleryChapterUpdate) return; + + evictTypeFields(cache, galleryChapterMutationImpactedTypeFields); + evictQueries(cache, galleryChapterMutationImpactedQueries); + }, }); + export const useGalleryChapterDestroy = () => GQL.useGalleryChapterDestroyMutation({ - refetchQueries: getQueryNames([GQL.FindGalleryDocument]), - update: deleteCache(galleryChapterMutationImpactedQueries), + update(cache, result, { variables }) { + if (!result.data?.galleryChapterDestroy || !variables) return; + + const obj = { __typename: "GalleryChapter", id: variables.id }; + cache.evict({ id: cache.identify(obj) }); + + evictTypeFields(cache, galleryChapterMutationImpactedTypeFields); + evictQueries(cache, galleryChapterMutationImpactedQueries); + }, }); -export const studioMutationImpactedQueries = [ - GQL.FindStudiosDocument, - GQL.FindSceneDocument, - GQL.FindScenesDocument, - GQL.AllStudiosForFilterDocument, +const performerMutationImpactedTypeFields = { + Tag: ["performer_count"], +}; + +const performerMutationImpactedQueries = [ + GQL.FindScenesDocument, // filter by performer tags + GQL.FindImagesDocument, // filter by performer tags + GQL.FindGalleriesDocument, // filter by performer tags + GQL.FindPerformersDocument, // various filters + GQL.FindTagsDocument, // filter by performer count ]; -export const mutateDeleteFiles = (ids: string[]) => - client.mutate({ - mutation: GQL.DeleteFilesDocument, - variables: { - ids, +export const usePerformerCreate = () => + GQL.usePerformerCreateMutation({ + update(cache, result) { + const performer = result.data?.performerCreate; + if (!performer) return; + + appendObject(cache, performer, GQL.AllPerformersForFilterDocument); + + // update stats + updateStats(cache, "performer_count", 1); + + evictTypeFields(cache, performerMutationImpactedTypeFields); + evictQueries(cache, [ + GQL.FindPerformersDocument, // various filters + GQL.FindTagsDocument, // filter by performer count + ]); }, - update: deleteCache([ - ...sceneMutationImpactedQueries, - ...imageMutationImpactedQueries, - ...galleryMutationImpactedQueries, - ]), - refetchQueries: getQueryNames([ - GQL.FindSceneDocument, - GQL.FindImageDocument, - GQL.FindGalleryDocument, - ]), }); +export const usePerformerUpdate = () => + GQL.usePerformerUpdateMutation({ + update(cache, result) { + if (!result.data?.performerUpdate) return; + + evictTypeFields(cache, performerMutationImpactedTypeFields); + evictQueries(cache, performerMutationImpactedQueries); + }, + }); + +export const useBulkPerformerUpdate = (input: GQL.BulkPerformerUpdateInput) => + GQL.useBulkPerformerUpdateMutation({ + variables: { input }, + update(cache, result) { + if (!result.data?.bulkPerformerUpdate) return; + + evictTypeFields(cache, performerMutationImpactedTypeFields); + evictQueries(cache, performerMutationImpactedQueries); + }, + }); + +export const usePerformerDestroy = () => + GQL.usePerformerDestroyMutation({ + update(cache, result, { variables }) { + if (!result.data?.performerDestroy || !variables) return; + + const obj = { __typename: "Performer", id: variables.id }; + deleteObject(cache, obj, GQL.FindPerformerDocument); + + // update stats + updateStats(cache, "performer_count", -1); + + evictTypeFields(cache, { + ...performerMutationImpactedTypeFields, + Performer: ["performer_count"], + Studio: ["performer_count"], + }); + evictQueries(cache, [ + ...performerMutationImpactedQueries, + GQL.FindPerformersDocument, // appears with + GQL.FindMoviesDocument, // filter by performers + GQL.FindSceneMarkersDocument, // filter by performers + ]); + }, + }); + +export const usePerformersDestroy = ( + input: GQL.PerformersDestroyMutationVariables +) => + GQL.usePerformersDestroyMutation({ + variables: input, + update(cache, result) { + if (!result.data?.performersDestroy) return; + + const { ids } = input; + + let count: number; + if (Array.isArray(ids)) { + for (const id of ids) { + const obj = { __typename: "Performer", id }; + deleteObject(cache, obj, GQL.FindPerformerDocument); + } + count = ids.length; + } else { + const obj = { __typename: "Performer", id: ids }; + deleteObject(cache, obj, GQL.FindPerformerDocument); + count = 1; + } + + // update stats + updateStats(cache, "performer_count", -count); + + evictTypeFields(cache, { + ...performerMutationImpactedTypeFields, + Performer: ["performer_count"], + Studio: ["performer_count"], + }); + evictQueries(cache, [ + ...performerMutationImpactedQueries, + GQL.FindPerformersDocument, // appears with + GQL.FindMoviesDocument, // filter by performers + GQL.FindSceneMarkersDocument, // filter by performers + ]); + }, + }); + +const studioMutationImpactedTypeFields = { + Studio: ["child_studios"], +}; + +const studioMutationImpactedQueries = [ + GQL.FindScenesDocument, // filter by studio + GQL.FindImagesDocument, // filter by studio + GQL.FindMoviesDocument, // filter by studio + GQL.FindGalleriesDocument, // filter by studio + GQL.FindPerformersDocument, // filter by studio + GQL.FindStudiosDocument, // various filters +]; + export const useStudioCreate = () => GQL.useStudioCreateMutation({ - refetchQueries: getQueryNames([GQL.AllStudiosForFilterDocument]), - update: deleteCache([ - GQL.FindStudiosDocument, - GQL.AllStudiosForFilterDocument, - ]), + update(cache, result, { variables }) { + const studio = result.data?.studioCreate; + if (!studio || !variables) return; + + appendObject(cache, studio, GQL.AllStudiosForFilterDocument); + + // update stats + updateStats(cache, "studio_count", 1); + + // if new scene has a parent studio, + // refetch the parent's list of child studios + const { parent_id } = variables.input; + if (parent_id !== undefined) { + cache.evict({ + id: cache.identify({ __typename: "Studio", id: parent_id }), + fieldName: "child_studios", + }); + } + + evictQueries(cache, [ + GQL.FindStudiosDocument, // various filters + ]); + }, }); export const useStudioUpdate = () => GQL.useStudioUpdateMutation({ - update: deleteCache(studioMutationImpactedQueries), + update(cache, result) { + const studio = result.data?.studioUpdate; + if (!studio) return; + + const obj = { __typename: "Studio", id: studio.id }; + evictTypeFields( + cache, + studioMutationImpactedTypeFields, + cache.identify(obj) // don't evict this studio + ); + + evictQueries(cache, studioMutationImpactedQueries); + }, }); export const useStudioDestroy = (input: GQL.StudioDestroyInput) => GQL.useStudioDestroyMutation({ variables: input, - update: deleteCache(studioMutationImpactedQueries), + update(cache, result) { + if (!result.data?.studioDestroy) return; + + const obj = { __typename: "Studio", id: input.id }; + deleteObject(cache, obj, GQL.FindStudioDocument); + + // update stats + updateStats(cache, "studio_count", -1); + + evictTypeFields(cache, studioMutationImpactedTypeFields); + evictQueries(cache, studioMutationImpactedQueries); + }, }); export const useStudiosDestroy = (input: GQL.StudiosDestroyMutationVariables) => GQL.useStudiosDestroyMutation({ variables: input, - update: deleteCache(studioMutationImpactedQueries), - }); + update(cache, result) { + if (!result.data?.studiosDestroy) return; -export const movieMutationImpactedQueries = [ - GQL.FindSceneDocument, - GQL.FindScenesDocument, - GQL.FindMoviesDocument, - GQL.AllMoviesForFilterDocument, -]; + const { ids } = input; -export const useMovieCreate = () => - GQL.useMovieCreateMutation({ - update: deleteCache([ - GQL.FindMoviesDocument, - GQL.AllMoviesForFilterDocument, - ]), - }); + for (const id of ids) { + const obj = { __typename: "Studio", id }; + deleteObject(cache, obj, GQL.FindStudioDocument); + } -export const useMovieUpdate = () => - GQL.useMovieUpdateMutation({ - update: deleteCache(movieMutationImpactedQueries), - }); + // update stats + updateStats(cache, "studio_count", -ids.length); -export const useBulkMovieUpdate = (input: GQL.BulkMovieUpdateInput) => - GQL.useBulkMovieUpdateMutation({ - variables: { - input, + evictTypeFields(cache, studioMutationImpactedTypeFields); + evictQueries(cache, studioMutationImpactedQueries); }, - update: deleteCache(movieMutationImpactedQueries), }); -export const useMovieDestroy = (input: GQL.MovieDestroyInput) => - GQL.useMovieDestroyMutation({ - variables: input, - update: deleteCache(movieMutationImpactedQueries), - }); +const tagMutationImpactedTypeFields = { + Tag: ["parents", "children"], +}; -export const useMoviesDestroy = (input: GQL.MoviesDestroyMutationVariables) => - GQL.useMoviesDestroyMutation({ - variables: input, - update: deleteCache(movieMutationImpactedQueries), - }); - -export const tagMutationImpactedQueries = [ - GQL.FindSceneDocument, - GQL.FindScenesDocument, - GQL.FindSceneMarkersDocument, - GQL.AllTagsForFilterDocument, - GQL.FindTagsDocument, +const tagMutationImpactedQueries = [ + GQL.FindScenesDocument, // filter by tags + GQL.FindImagesDocument, // filter by tags + GQL.FindGalleriesDocument, // filter by tags + GQL.FindPerformersDocument, // filter by tags + GQL.FindTagsDocument, // various filters ]; export const useTagCreate = () => GQL.useTagCreateMutation({ - refetchQueries: getQueryNames([ - GQL.AllTagsForFilterDocument, - GQL.FindTagsDocument, - ]), - update: deleteCache([GQL.AllTagsForFilterDocument, GQL.FindTagsDocument]), + update(cache, result) { + const tag = result.data?.tagCreate; + if (!tag) return; + + appendObject(cache, tag, GQL.AllTagsForFilterDocument); + + // update stats + updateStats(cache, "tag_count", 1); + + const obj = { __typename: "Tag", id: tag.id }; + evictTypeFields( + cache, + tagMutationImpactedTypeFields, + cache.identify(obj) // don't evict this tag + ); + + evictQueries(cache, [ + GQL.FindTagsDocument, // various filters + ]); + }, }); + export const useTagUpdate = () => GQL.useTagUpdateMutation({ - update: deleteCache(tagMutationImpactedQueries), + update(cache, result) { + const tag = result.data?.tagUpdate; + if (!tag) return; + + const obj = { __typename: "Tag", id: tag.id }; + evictTypeFields( + cache, + tagMutationImpactedTypeFields, + cache.identify(obj) // don't evict this tag + ); + + evictQueries(cache, tagMutationImpactedQueries); + }, }); + export const useTagDestroy = (input: GQL.TagDestroyInput) => GQL.useTagDestroyMutation({ variables: input, - update: deleteCache(tagMutationImpactedQueries), + update(cache, result) { + if (!result.data?.tagDestroy) return; + + const obj = { __typename: "Tag", id: input.id }; + deleteObject(cache, obj, GQL.FindTagDocument); + + // update stats + updateStats(cache, "tag_count", -1); + + evictTypeFields(cache, tagMutationImpactedTypeFields); + evictQueries(cache, tagMutationImpactedQueries); + }, }); export const useTagsDestroy = (input: GQL.TagsDestroyMutationVariables) => GQL.useTagsDestroyMutation({ variables: input, - update: deleteCache(tagMutationImpactedQueries), - }); + update(cache, result) { + if (!result.data?.tagsDestroy) return; -export const useSceneSaveActivity = () => - GQL.useSceneSaveActivityMutation({ - update: deleteCache([GQL.FindScenesDocument]), - }); + const { ids } = input; -export const useSceneIncrementPlayCount = () => - GQL.useSceneIncrementPlayCountMutation({ - update: deleteCache([GQL.FindScenesDocument]), - }); + for (const id of ids) { + const obj = { __typename: "Tag", id }; + deleteObject(cache, obj, GQL.FindTagDocument); + } -export const savedFilterMutationImpactedQueries = [ - GQL.FindSavedFiltersDocument, -]; + // update stats + updateStats(cache, "tag_count", -ids.length); -export const useSaveFilter = () => - GQL.useSaveFilterMutation({ - update: deleteCache(savedFilterMutationImpactedQueries), - }); - -export const savedFilterDefaultMutationImpactedQueries = [ - GQL.FindDefaultFilterDocument, -]; - -export const useSetDefaultFilter = () => - GQL.useSetDefaultFilterMutation({ - update: deleteCache(savedFilterDefaultMutationImpactedQueries), - }); - -export const useSavedFilterDestroy = () => - GQL.useDestroySavedFilterMutation({ - update: deleteCache(savedFilterMutationImpactedQueries), + evictTypeFields(cache, tagMutationImpactedTypeFields); + evictQueries(cache, tagMutationImpactedQueries); + }, }); export const useTagsMerge = () => GQL.useTagsMergeMutation({ - update: deleteCache(tagMutationImpactedQueries), - }); + update(cache, result, { variables }) { + if (!result.data?.tagsMerge || !variables) return; -export const useConfigureGeneral = () => - GQL.useConfigureGeneralMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); + const { source, destination } = variables; -export const useConfigureInterface = () => - GQL.useConfigureInterfaceMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); + for (const id of source) { + const obj = { __typename: "Tag", id }; + deleteObject(cache, obj, GQL.FindTagDocument); + } -export const useGenerateAPIKey = () => - GQL.useGenerateApiKeyMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); + updateStats(cache, "tag_count", -source.length); -export const useConfigureDefaults = () => - GQL.useConfigureDefaultsMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); + const obj = { __typename: "Tag", id: destination }; + evictTypeFields( + cache, + tagMutationImpactedTypeFields, + cache.identify(obj) // don't evict destination tag + ); -export const useConfigureUI = () => - GQL.useConfigureUiMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); - -export const useJobsSubscribe = () => GQL.useJobsSubscribeSubscription(); - -export const useConfigureDLNA = () => - GQL.useConfigureDlnaMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); - -export const useEnableDLNA = () => GQL.useEnableDlnaMutation(); - -export const useDisableDLNA = () => GQL.useDisableDlnaMutation(); - -export const useAddTempDLNAIP = () => GQL.useAddTempDlnaipMutation(); - -export const useRemoveTempDLNAIP = () => GQL.useRemoveTempDlnaipMutation(); - -export const useLoggingSubscribe = () => GQL.useLoggingSubscribeSubscription(); - -export const useConfigureScraping = () => - GQL.useConfigureScrapingMutation({ - refetchQueries: getQueryNames([GQL.ConfigurationDocument]), - update: deleteCache([GQL.ConfigurationDocument]), - }); - -export const querySystemStatus = () => - client.query({ - query: GQL.SystemStatusDocument, - fetchPolicy: "no-cache", - }); - -export const useSystemStatus = () => - GQL.useSystemStatusQuery({ - fetchPolicy: "no-cache", - }); - -export const useLogs = () => - GQL.useLogsQuery({ - fetchPolicy: "no-cache", - }); - -export const queryLogs = () => - client.query({ - query: GQL.LogsDocument, - fetchPolicy: "no-cache", - }); - -export const useJobQueue = () => - GQL.useJobQueueQuery({ - fetchPolicy: "no-cache", - }); - -export const mutateStopJob = (jobID: string) => - client.mutate({ - mutation: GQL.StopJobDocument, - variables: { - job_id: jobID, + evictQueries(cache, tagMutationImpactedQueries); }, }); -export const useDLNAStatus = () => - GQL.useDlnaStatusQuery({ - fetchPolicy: "no-cache", +export const useSaveFilter = () => + GQL.useSaveFilterMutation({ + update(cache, result) { + if (!result.data?.saveFilter) return; + + evictQueries(cache, [GQL.FindSavedFiltersDocument]); + }, }); -export const queryScrapePerformer = ( - scraperId: string, - scrapedPerformer: GQL.ScrapedPerformerInput +export const useSetDefaultFilter = () => + GQL.useSetDefaultFilterMutation({ + update(cache, result) { + if (!result.data?.setDefaultFilter) return; + + evictQueries(cache, [GQL.FindDefaultFilterDocument]); + }, + }); + +export const useSavedFilterDestroy = () => + GQL.useDestroySavedFilterMutation({ + update(cache, result, { variables }) { + if (!result.data?.destroySavedFilter || !variables) return; + + const obj = { __typename: "SavedFilter", id: variables.input.id }; + deleteObject(cache, obj, GQL.FindSavedFilterDocument); + + evictQueries(cache, [GQL.FindDefaultFilterDocument]); + }, + }); + +export const mutateDeleteFiles = (ids: string[]) => + client.mutate({ + mutation: GQL.DeleteFilesDocument, + variables: { ids }, + update(cache, result) { + if (!result.data?.deleteFiles) return; + + // we don't know which type the files are, + // so evict all of them + for (const id of ids) { + cache.evict({ + id: cache.identify({ __typename: "VideoFile", id }), + }); + cache.evict({ + id: cache.identify({ __typename: "ImageFile", id }), + }); + cache.evict({ + id: cache.identify({ __typename: "GalleryFile", id }), + }); + } + + evictQueries(cache, [ + GQL.FindScenesDocument, // filter by file count + GQL.FindImagesDocument, // filter by file count + GQL.FindGalleriesDocument, // filter by file count + GQL.StatsDocument, // scenes size, images size + ]); + }, + }); + +/// Scrapers + +export const useListSceneScrapers = () => GQL.useListSceneScrapersQuery(); + +export const queryScrapeScene = ( + source: GQL.ScraperSourceInput, + sceneId: string ) => - client.query({ - query: GQL.ScrapeSinglePerformerDocument, + client.query({ + query: GQL.ScrapeSingleSceneDocument, variables: { - source: { - scraper_id: scraperId, - }, + source, input: { - performer_input: scrapedPerformer, + scene_id: sceneId, }, }, fetchPolicy: "network-only", }); -export const queryScrapePerformerURL = (url: string) => - client.query({ - query: GQL.ScrapePerformerUrlDocument, - variables: { - url, - }, - fetchPolicy: "network-only", - }); - export const queryScrapeSceneQuery = ( source: GQL.ScraperSourceInput, q: string @@ -1077,56 +1779,7 @@ export const queryScrapeSceneQuery = ( export const queryScrapeSceneURL = (url: string) => client.query({ query: GQL.ScrapeSceneUrlDocument, - variables: { - url, - }, - fetchPolicy: "network-only", - }); - -export const queryScrapeGalleryURL = (url: string) => - client.query({ - query: GQL.ScrapeGalleryUrlDocument, - variables: { - url, - }, - fetchPolicy: "network-only", - }); - -export const queryScrapeMovieURL = (url: string) => - client.query({ - query: GQL.ScrapeMovieUrlDocument, - variables: { - url, - }, - fetchPolicy: "network-only", - }); - -export const queryScrapeScene = ( - source: GQL.ScraperSourceInput, - sceneId: string -) => - client.query({ - query: GQL.ScrapeSingleSceneDocument, - variables: { - source, - input: { - scene_id: sceneId, - }, - }, - fetchPolicy: "network-only", - }); - -export const queryStashBoxScene = (stashBoxIndex: number, sceneID: string) => - client.query({ - query: GQL.ScrapeSingleSceneDocument, - variables: { - source: { - stash_box_index: stashBoxIndex, - }, - input: { - scene_id: sceneID, - }, - }, + variables: { url }, fetchPolicy: "network-only", }); @@ -1145,6 +1798,97 @@ export const queryScrapeSceneQueryFragment = ( fetchPolicy: "network-only", }); +export const stashBoxSceneBatchQuery = ( + sceneIds: string[], + stashBoxIndex: number +) => + client.query({ + query: GQL.ScrapeMultiScenesDocument, + variables: { + source: { + stash_box_index: stashBoxIndex, + }, + input: { + scene_ids: sceneIds, + }, + }, + }); + +export const useListPerformerScrapers = () => + GQL.useListPerformerScrapersQuery(); + +export const useScrapePerformerList = (scraperId: string, q: string) => + GQL.useScrapeSinglePerformerQuery({ + variables: { + source: { + scraper_id: scraperId, + }, + input: { + query: q, + }, + }, + skip: q === "", + }); + +export const queryScrapePerformer = ( + scraperId: string, + scrapedPerformer: GQL.ScrapedPerformerInput +) => + client.query({ + query: GQL.ScrapeSinglePerformerDocument, + variables: { + source: { + scraper_id: scraperId, + }, + input: { + performer_input: scrapedPerformer, + }, + }, + fetchPolicy: "network-only", + }); + +export const queryScrapePerformerURL = (url: string) => + client.query({ + query: GQL.ScrapePerformerUrlDocument, + variables: { url }, + fetchPolicy: "network-only", + }); + +export const stashBoxPerformerQuery = ( + searchVal: string, + stashBoxIndex: number +) => + client.query({ + query: GQL.ScrapeSinglePerformerDocument, + variables: { + source: { + stash_box_index: stashBoxIndex, + }, + input: { + query: searchVal, + }, + }, + }); + +export const mutateStashBoxBatchPerformerTag = ( + input: GQL.StashBoxBatchPerformerTagInput +) => + client.mutate({ + mutation: GQL.StashBoxBatchPerformerTagDocument, + variables: { input }, + }); + +export const useListMovieScrapers = () => GQL.useListMovieScrapersQuery(); + +export const queryScrapeMovieURL = (url: string) => + client.query({ + query: GQL.ScrapeMovieUrlDocument, + variables: { url }, + fetchPolicy: "network-only", + }); + +export const useListGalleryScrapers = () => GQL.useListGalleryScrapersQuery(); + export const queryScrapeGallery = (scraperId: string, galleryId: string) => client.query({ query: GQL.ScrapeSingleGalleryDocument, @@ -1159,6 +1903,116 @@ export const queryScrapeGallery = (scraperId: string, galleryId: string) => fetchPolicy: "network-only", }); +export const queryScrapeGalleryURL = (url: string) => + client.query({ + query: GQL.ScrapeGalleryUrlDocument, + variables: { url }, + fetchPolicy: "network-only", + }); + +/// Configuration + +export const useConfiguration = () => GQL.useConfigurationQuery(); + +export const usePlugins = () => GQL.usePluginsQuery(); + +export const usePluginTasks = () => GQL.usePluginTasksQuery(); + +export const useStats = () => GQL.useStatsQuery(); + +export const useVersion = () => GQL.useVersionQuery(); + +export const useLatestVersion = () => + GQL.useLatestVersionQuery({ + notifyOnNetworkStatusChange: true, + errorPolicy: "ignore", + }); + +export const useDLNAStatus = () => + GQL.useDlnaStatusQuery({ + fetchPolicy: "no-cache", + }); + +export const useJobQueue = () => + GQL.useJobQueueQuery({ + fetchPolicy: "no-cache", + }); + +export const useLogs = () => + GQL.useLogsQuery({ + fetchPolicy: "no-cache", + }); + +export const queryLogs = () => + client.query({ + query: GQL.LogsDocument, + fetchPolicy: "no-cache", + }); + +export const useSystemStatus = () => + GQL.useSystemStatusQuery({ + fetchPolicy: "no-cache", + }); + +export const querySystemStatus = () => + client.query({ + query: GQL.SystemStatusDocument, + fetchPolicy: "no-cache", + }); + +export const useJobsSubscribe = () => GQL.useJobsSubscribeSubscription(); + +export const useLoggingSubscribe = () => GQL.useLoggingSubscribeSubscription(); + +function updateConfiguration(cache: ApolloCache, result: FetchResult) { + if (!result.data) return; + + evictQueries(cache, [GQL.ConfigurationDocument]); +} + +export const useConfigureGeneral = () => + GQL.useConfigureGeneralMutation({ + update: updateConfiguration, + }); + +export const useConfigureInterface = () => + GQL.useConfigureInterfaceMutation({ + update: updateConfiguration, + }); + +export const useGenerateAPIKey = () => + GQL.useGenerateApiKeyMutation({ + update: updateConfiguration, + }); + +export const useConfigureDefaults = () => + GQL.useConfigureDefaultsMutation({ + update: updateConfiguration, + }); + +export const useConfigureUI = () => + GQL.useConfigureUiMutation({ + update: updateConfiguration, + }); + +export const useConfigureScraping = () => + GQL.useConfigureScrapingMutation({ + update: updateConfiguration, + }); + +export const useConfigureDLNA = () => + GQL.useConfigureDlnaMutation({ + update: updateConfiguration, + }); + +export const useEnableDLNA = () => GQL.useEnableDlnaMutation(); + +export const useDisableDLNA = () => GQL.useDisableDlnaMutation(); + +export const useAddTempDLNAIP = () => GQL.useAddTempDlnaipMutation(); + +export const useRemoveTempDLNAIP = () => GQL.useRemoveTempDlnaipMutation(); + export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, @@ -1175,22 +2029,53 @@ export const mutateReloadPlugins = () => refetchQueries: [GQL.refetchPluginsQuery(), GQL.refetchPluginTasksQuery()], }); -export const mutateRunPluginTask = ( - pluginId: string, - taskName: string, - args?: GQL.PluginArgInput[] -) => - client.mutate({ - mutation: GQL.RunPluginTaskDocument, - variables: { plugin_id: pluginId, task_name: taskName, args }, +export const mutateStopJob = (jobID: string) => + client.mutate({ + mutation: GQL.StopJobDocument, + variables: { job_id: jobID }, }); +const setupMutationImpactedQueries = [ + GQL.ConfigurationDocument, + GQL.SystemStatusDocument, +]; + +export const mutateSetup = (input: GQL.SetupInput) => + client.mutate({ + mutation: GQL.SetupDocument, + variables: { input }, + update(cache, result) { + if (!result.data?.setup) return; + + evictQueries(cache, setupMutationImpactedQueries); + }, + }); + +export const mutateMigrate = (input: GQL.MigrateInput) => + client.mutate({ + mutation: GQL.MigrateDocument, + variables: { input }, + update(cache, result) { + if (!result.data?.migrate) return; + + evictQueries(cache, setupMutationImpactedQueries); + }, + }); + +/// Tasks + export const mutateMetadataScan = (input: GQL.ScanMetadataInput) => client.mutate({ mutation: GQL.MetadataScanDocument, variables: { input }, }); +export const mutateMetadataIdentify = (input: GQL.IdentifyMetadataInput) => + client.mutate({ + mutation: GQL.MetadataIdentifyDocument, + variables: { input }, + }); + export const mutateMetadataAutoTag = (input: GQL.AutoTagMetadataInput) => client.mutate({ mutation: GQL.MetadataAutoTagDocument, @@ -1209,29 +2094,14 @@ export const mutateMetadataClean = (input: GQL.CleanMetadataInput) => variables: { input }, }); -export const mutateMetadataIdentify = (input: GQL.IdentifyMetadataInput) => - client.mutate({ - mutation: GQL.MetadataIdentifyDocument, - variables: { input }, - }); - -export const mutateMigrateHashNaming = () => - client.mutate({ - mutation: GQL.MigrateHashNamingDocument, - }); - -export const mutateMigrateSceneScreenshots = ( - input: GQL.MigrateSceneScreenshotsInput +export const mutateRunPluginTask = ( + pluginId: string, + taskName: string, + args?: GQL.PluginArgInput[] ) => - client.mutate({ - mutation: GQL.MigrateSceneScreenshotsDocument, - variables: { input }, - }); - -export const mutateMigrateBlobs = (input: GQL.MigrateBlobsInput) => - client.mutate({ - mutation: GQL.MigrateBlobsDocument, - variables: { input }, + client.mutate({ + mutation: GQL.RunPluginTaskDocument, + variables: { plugin_id: pluginId, task_name: taskName, args }, }); export const mutateMetadataExport = () => @@ -1268,20 +2138,30 @@ export const mutateAnonymiseDatabase = (input: GQL.AnonymiseDatabaseInput) => variables: { input }, }); -export const mutateStashBoxBatchPerformerTag = ( - input: GQL.StashBoxBatchPerformerTagInput +export const mutateMigrateHashNaming = () => + client.mutate({ + mutation: GQL.MigrateHashNamingDocument, + }); + +export const mutateMigrateSceneScreenshots = ( + input: GQL.MigrateSceneScreenshotsInput ) => - client.mutate({ - mutation: GQL.StashBoxBatchPerformerTagDocument, + client.mutate({ + mutation: GQL.MigrateSceneScreenshotsDocument, variables: { input }, }); -export const querySceneByPathRegex = (filter: GQL.FindFilterType) => - client.query({ - query: GQL.FindScenesByPathRegexDocument, - variables: { filter }, +export const mutateMigrateBlobs = (input: GQL.MigrateBlobsInput) => + client.mutate({ + mutation: GQL.MigrateBlobsDocument, + variables: { input }, }); +/// Misc + +export const useDirectory = (path?: string) => + GQL.useDirectoryQuery({ variables: { path } }); + export const queryParseSceneFilenames = ( filter: GQL.FindFilterType, config: GQL.SceneParserInput @@ -1326,80 +2206,3 @@ export const makePerformerCreateInput = (toCreate: GQL.ScrapedPerformer) => { }; return input; }; - -export const stashBoxSceneQuery = (searchVal: string, stashBoxIndex: number) => - client.query({ - query: GQL.ScrapeSingleSceneDocument, - variables: { - source: { - stash_box_index: stashBoxIndex, - }, - input: { - query: searchVal, - }, - }, - }); - -export const stashBoxPerformerQuery = ( - searchVal: string, - stashBoxIndex: number -) => - client.query({ - query: GQL.ScrapeSinglePerformerDocument, - variables: { - source: { - stash_box_index: stashBoxIndex, - }, - input: { - query: searchVal, - }, - }, - }); - -export const stashBoxSceneBatchQuery = ( - sceneIds: string[], - stashBoxIndex: number -) => - client.query({ - query: GQL.ScrapeMultiScenesDocument, - variables: { - source: { - stash_box_index: stashBoxIndex, - }, - input: { - scene_ids: sceneIds, - }, - }, - }); - -export const stashBoxPerformerBatchQuery = ( - performerIds: string[], - stashBoxIndex: number -) => - client.query({ - query: GQL.ScrapeMultiPerformersDocument, - variables: { - source: { - stash_box_index: stashBoxIndex, - }, - input: { - performer_ids: performerIds, - }, - }, - }); - -export const stashBoxSubmitSceneDraft = ( - input: GQL.StashBoxDraftSubmissionInput -) => - client.mutate({ - mutation: GQL.SubmitStashBoxSceneDraftDocument, - variables: { input }, - }); - -export const stashBoxSubmitPerformerDraft = ( - input: GQL.StashBoxDraftSubmissionInput -) => - client.mutate({ - mutation: GQL.SubmitStashBoxPerformerDraftDocument, - variables: { input }, - }); diff --git a/ui/v2.5/src/core/createClient.ts b/ui/v2.5/src/core/createClient.ts index b6601a6cc..a3912177f 100644 --- a/ui/v2.5/src/core/createClient.ts +++ b/ui/v2.5/src/core/createClient.ts @@ -12,60 +12,55 @@ import { onError } from "@apollo/client/link/error"; import { getMainDefinition } from "@apollo/client/utilities"; import { createUploadLink } from "apollo-upload-client"; import * as GQL from "src/core/generated-graphql"; +import { FieldReadFunction } from "@apollo/client/cache"; + +// A read function that returns a cache reference with the given +// typename if no valid reference is available. +// Allows to return a cached object rather than fetching. +const readReference = (typename: string): FieldReadFunction => { + return (existing, { args, canRead, toReference }) => + canRead(existing) + ? existing + : toReference({ + __typename: typename, + id: args?.id, + }); +}; + +// A read function that returns null if no valid reference is available. +// Means that a dangling reference implies the object was deleted. +const readDanglingNull: FieldReadFunction = (existing, { canRead }) => + canRead(existing) ? existing : null; -// Policies that tell apollo what the type of the returned object will be. -// In many cases this allows it to return from cache immediately rather than fetching. const typePolicies: TypePolicies = { Query: { fields: { findImage: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Image", - id: args?.id, - }), + read: readReference("Image"), }, findPerformer: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Performer", - id: args?.id, - }), + read: readReference("Performer"), }, findStudio: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Studio", - id: args?.id, - }), + read: readReference("Studio"), }, findMovie: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Movie", - id: args?.id, - }), + read: readReference("Movie"), }, findGallery: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Gallery", - id: args?.id, - }), + read: readReference("Gallery"), }, findScene: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Scene", - id: args?.id, - }), + read: readReference("Scene"), }, findTag: { - read: (_, { args, toReference }) => - toReference({ - __typename: "Tag", - id: args?.id, - }), + read: readReference("Tag"), + }, + findSavedFilter: { + read: readReference("SavedFilter"), + }, + findDefaultFilter: { + read: readDanglingNull, }, }, }, @@ -74,6 +69,37 @@ const typePolicies: TypePolicies = { scene_markers: { merge: false, }, + studio: { + read: readDanglingNull, + }, + }, + }, + Image: { + fields: { + studio: { + read: readDanglingNull, + }, + }, + }, + Movie: { + fields: { + studio: { + read: readDanglingNull, + }, + }, + }, + Gallery: { + fields: { + studio: { + read: readDanglingNull, + }, + }, + }, + Studio: { + fields: { + parent_studio: { + read: readDanglingNull, + }, }, }, Tag: { @@ -89,6 +115,7 @@ const typePolicies: TypePolicies = { }; const possibleTypes = { + BaseFile: ["VideoFile", "ImageFile", "GalleryFile"], VisualFile: ["VideoFile", "ImageFile"], }; diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index 0ee4a8e75..ff8eac591 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -21,18 +21,18 @@ resize-observer-polyfill "^1.5.1" throttle-debounce "^5.0.0" -"@apollo/client@^3.7.0", "@apollo/client@^3.7.8": - version "3.7.8" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.8.tgz#e1c8dfd02cbbe1baf9b18fa86918904efd9cc580" - integrity sha512-o1NxF4ytET2w9HSVMLwYUEEdv6H3XPpbh9M+ABVGnUVT0s6T9pgqRtYO4pFP1TmeDmb1pbRfVhFwh3gC167j5Q== +"@apollo/client@^3.7.0", "@apollo/client@^3.7.17": + version "3.7.17" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.7.17.tgz#1d2538729fd8ef138aa301a7cf62704474e57b72" + integrity sha512-0EErSHEtKPNl5wgWikHJbKFAzJ/k11O0WO2QyqZSHpdxdAnw7UWHY4YiLbHCFG7lhrD+NTQ3Z/H9Jn4rcikoJA== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/context" "^0.7.0" "@wry/equality" "^0.5.0" - "@wry/trie" "^0.3.0" + "@wry/trie" "^0.4.0" graphql-tag "^2.12.6" hoist-non-react-statics "^3.3.2" - optimism "^0.16.1" + optimism "^0.16.2" prop-types "^15.7.2" response-iterator "^0.2.6" symbol-observable "^4.0.0" @@ -2718,6 +2718,13 @@ dependencies: tslib "^2.3.0" +"@wry/trie@^0.4.0": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@wry/trie/-/trie-0.4.3.tgz#077d52c22365871bf3ffcbab8e95cb8bc5689af4" + integrity sha512-I6bHwH0fSf6RqQcnnXLJKhkSXG45MFral3GxPaY4uAl0LYDZM+YDVDAiU9bYwjTuysy1S0IeecWtmq1SZA3M1w== + dependencies: + tslib "^2.3.0" + "@xmldom/xmldom@^0.8.3": version "0.8.6" resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440" @@ -6197,7 +6204,7 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -optimism@^0.16.1: +optimism@^0.16.2: version "0.16.2" resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.16.2.tgz#519b0c78b3b30954baed0defe5143de7776bf081" integrity sha512-zWNbgWj+3vLEjZNIh/okkY2EUfX+vB9TJopzIZwT1xxaMqC5hRLLraePod4c5n4He08xuXNH+zhKFFCu390wiQ==