From d1018b4c5df9a9fc7861aedebf83629d360872e0 Mon Sep 17 00:00:00 2001 From: bob123491234 <54259225+bob123491234@users.noreply.github.com> Date: Mon, 27 Nov 2023 22:05:33 -0600 Subject: [PATCH] Add Studio Code and Photographer to Galleries. (#4195) * Added Studio Code and Photographer to Galleries * Fix gallery display on mobile * Fixed potential panic when scraping with a bad configuration --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com> --- graphql/documents/data/gallery-slim.graphql | 2 + graphql/documents/data/gallery.graphql | 2 + graphql/documents/data/scrapers.graphql | 2 + graphql/schema/types/filters.graphql | 4 + graphql/schema/types/gallery.graphql | 8 ++ graphql/schema/types/scraper.graphql | 4 + internal/api/resolver_mutation_gallery.go | 6 + pkg/gallery/export.go | 12 +- pkg/gallery/import.go | 6 + pkg/models/gallery.go | 16 ++- pkg/models/jsonschema/gallery.go | 30 ++--- pkg/models/model_gallery.go | 18 +-- pkg/scraper/gallery.go | 26 +++-- pkg/scraper/mapped.go | 15 ++- pkg/sqlite/database.go | 2 +- pkg/sqlite/gallery.go | 18 ++- pkg/sqlite/gallery_test.go | 104 ++++++++++-------- .../53_gallery_photographer_code.up.sql | 2 + .../GalleryDetails/GalleryDetailPanel.tsx | 12 +- .../GalleryDetails/GalleryEditPanel.tsx | 14 +++ .../GalleryDetails/GalleryScrapeDialog.tsx | 32 +++++- ui/v2.5/src/components/Galleries/styles.scss | 6 +- ui/v2.5/src/locales/en-GB.json | 1 + ui/v2.5/src/models/list-filter/galleries.ts | 2 + ui/v2.5/src/models/list-filter/types.ts | 1 + 25 files changed, 243 insertions(+), 102 deletions(-) create mode 100644 pkg/sqlite/migrations/53_gallery_photographer_code.up.sql diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql index ea44b2997..fd2688777 100644 --- a/graphql/documents/data/gallery-slim.graphql +++ b/graphql/documents/data/gallery-slim.graphql @@ -1,9 +1,11 @@ fragment SlimGalleryData on Gallery { id title + code date urls details + photographer rating100 organized files { diff --git a/graphql/documents/data/gallery.graphql b/graphql/documents/data/gallery.graphql index 89719dfca..5a97f77c5 100644 --- a/graphql/documents/data/gallery.graphql +++ b/graphql/documents/data/gallery.graphql @@ -3,9 +3,11 @@ fragment GalleryData on Gallery { created_at updated_at title + code date urls details + photographer rating100 organized diff --git a/graphql/documents/data/scrapers.graphql b/graphql/documents/data/scrapers.graphql index 05ed76b5c..94b6434b1 100644 --- a/graphql/documents/data/scrapers.graphql +++ b/graphql/documents/data/scrapers.graphql @@ -184,8 +184,10 @@ fragment ScrapedSceneData on ScrapedScene { fragment ScrapedGalleryData on ScrapedGallery { title + code details urls + photographer date studio { diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index 9d1f5ebe6..f0c8e4988 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -377,6 +377,10 @@ input GalleryFilterType { created_at: TimestampCriterionInput "Filter by last update time" updated_at: TimestampCriterionInput + "Filter by studio code" + code: StringCriterionInput + "Filter by photographer" + photographer: StringCriterionInput } input TagFilterType { diff --git a/graphql/schema/types/gallery.graphql b/graphql/schema/types/gallery.graphql index c4e3efb89..47f6c7d7e 100644 --- a/graphql/schema/types/gallery.graphql +++ b/graphql/schema/types/gallery.graphql @@ -2,10 +2,12 @@ type Gallery { id: ID! title: String + code: String url: String @deprecated(reason: "Use urls") urls: [String!]! date: String details: String + photographer: String # rating expressed as 1-100 rating100: Int organized: Boolean! @@ -27,10 +29,12 @@ type Gallery { input GalleryCreateInput { title: String! + code: String url: String @deprecated(reason: "Use urls") urls: [String!] date: String details: String + photographer: String # rating expressed as 1-100 rating100: Int organized: Boolean @@ -44,10 +48,12 @@ input GalleryUpdateInput { clientMutationId: String id: ID! title: String + code: String url: String @deprecated(reason: "Use urls") urls: [String!] date: String details: String + photographer: String # rating expressed as 1-100 rating100: Int organized: Boolean @@ -62,10 +68,12 @@ input GalleryUpdateInput { input BulkGalleryUpdateInput { clientMutationId: String ids: [ID!] + code: String url: String @deprecated(reason: "Use urls") urls: BulkUpdateStrings date: String details: String + photographer: String # rating expressed as 1-100 rating100: Int organized: Boolean diff --git a/graphql/schema/types/scraper.graphql b/graphql/schema/types/scraper.graphql index 320d6065c..958aff5d2 100644 --- a/graphql/schema/types/scraper.graphql +++ b/graphql/schema/types/scraper.graphql @@ -99,7 +99,9 @@ input ScrapedSceneInput { type ScrapedGallery { title: String + code: String details: String + photographer: String url: String @deprecated(reason: "use urls") urls: [String!] date: String @@ -111,7 +113,9 @@ type ScrapedGallery { input ScrapedGalleryInput { title: String + code: String details: String + photographer: String url: String @deprecated(reason: "use urls") urls: [String!] date: String diff --git a/internal/api/resolver_mutation_gallery.go b/internal/api/resolver_mutation_gallery.go index a7bfe2332..a72531152 100644 --- a/internal/api/resolver_mutation_gallery.go +++ b/internal/api/resolver_mutation_gallery.go @@ -43,7 +43,9 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat newGallery := models.NewGallery() newGallery.Title = input.Title + newGallery.Code = translator.string(input.Code) newGallery.Details = translator.string(input.Details) + newGallery.Photographer = translator.string(input.Photographer) newGallery.Rating = input.Rating100 var err error @@ -182,7 +184,9 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle updatedGallery.Title = models.NewOptionalString(*input.Title) } + updatedGallery.Code = translator.optionalString(input.Code, "code") updatedGallery.Details = translator.optionalString(input.Details, "details") + updatedGallery.Photographer = translator.optionalString(input.Photographer, "photographer") updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100") updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") @@ -257,7 +261,9 @@ func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input BulkGall // Populate gallery from the input updatedGallery := models.NewGalleryPartial() + updatedGallery.Code = translator.optionalString(input.Code, "code") updatedGallery.Details = translator.optionalString(input.Details, "details") + updatedGallery.Photographer = translator.optionalString(input.Photographer, "photographer") updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100") updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") updatedGallery.URLs = translator.optionalURLsBulk(input.Urls, input.URL) diff --git a/pkg/gallery/export.go b/pkg/gallery/export.go index 5412e9a50..00d91bc98 100644 --- a/pkg/gallery/export.go +++ b/pkg/gallery/export.go @@ -13,11 +13,13 @@ import ( // does not convert the relationships to other objects. func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) { newGalleryJSON := jsonschema.Gallery{ - Title: gallery.Title, - URLs: gallery.URLs.List(), - Details: gallery.Details, - CreatedAt: json.JSONTime{Time: gallery.CreatedAt}, - UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt}, + Title: gallery.Title, + Code: gallery.Code, + URLs: gallery.URLs.List(), + Details: gallery.Details, + Photographer: gallery.Photographer, + CreatedAt: json.JSONTime{Time: gallery.CreatedAt}, + UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt}, } if gallery.FolderID != nil { diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go index fbfd58b94..c332e0ce0 100644 --- a/pkg/gallery/import.go +++ b/pkg/gallery/import.go @@ -62,9 +62,15 @@ func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.G if galleryJSON.Title != "" { newGallery.Title = galleryJSON.Title } + if galleryJSON.Code != "" { + newGallery.Code = galleryJSON.Code + } if galleryJSON.Details != "" { newGallery.Details = galleryJSON.Details } + if galleryJSON.Photographer != "" { + newGallery.Photographer = galleryJSON.Photographer + } if len(galleryJSON.URLs) > 0 { newGallery.URLs = models.NewRelatedStrings(galleryJSON.URLs) } else if galleryJSON.URL != "" { diff --git a/pkg/models/gallery.go b/pkg/models/gallery.go index bdff9c7fb..02e492876 100644 --- a/pkg/models/gallery.go +++ b/pkg/models/gallery.go @@ -1,12 +1,14 @@ package models type GalleryFilterType struct { - And *GalleryFilterType `json:"AND"` - Or *GalleryFilterType `json:"OR"` - Not *GalleryFilterType `json:"NOT"` - ID *IntCriterionInput `json:"id"` - Title *StringCriterionInput `json:"title"` - Details *StringCriterionInput `json:"details"` + And *GalleryFilterType `json:"AND"` + Or *GalleryFilterType `json:"OR"` + Not *GalleryFilterType `json:"NOT"` + ID *IntCriterionInput `json:"id"` + Title *StringCriterionInput `json:"title"` + Code *StringCriterionInput `json:"code"` + Details *StringCriterionInput `json:"details"` + Photographer *StringCriterionInput `json:"photographer"` // Filter by file checksum Checksum *StringCriterionInput `json:"checksum"` // Filter by path @@ -57,9 +59,11 @@ type GalleryUpdateInput struct { ClientMutationID *string `json:"clientMutationId"` ID string `json:"id"` Title *string `json:"title"` + Code *string `json:"code"` Urls []string `json:"urls"` Date *string `json:"date"` Details *string `json:"details"` + Photographer *string `json:"photographer"` Rating100 *int `json:"rating100"` Organized *bool `json:"organized"` SceneIds []string `json:"scene_ids"` diff --git a/pkg/models/jsonschema/gallery.go b/pkg/models/jsonschema/gallery.go index 0832cc07a..7323e37ba 100644 --- a/pkg/models/jsonschema/gallery.go +++ b/pkg/models/jsonschema/gallery.go @@ -18,20 +18,22 @@ type GalleryChapter struct { } type Gallery struct { - ZipFiles []string `json:"zip_files,omitempty"` - FolderPath string `json:"folder_path,omitempty"` - Title string `json:"title,omitempty"` - URLs []string `json:"urls,omitempty"` - Date string `json:"date,omitempty"` - Details string `json:"details,omitempty"` - Rating int `json:"rating,omitempty"` - Organized bool `json:"organized,omitempty"` - Chapters []GalleryChapter `json:"chapters,omitempty"` - Studio string `json:"studio,omitempty"` - Performers []string `json:"performers,omitempty"` - Tags []string `json:"tags,omitempty"` - CreatedAt json.JSONTime `json:"created_at,omitempty"` - UpdatedAt json.JSONTime `json:"updated_at,omitempty"` + ZipFiles []string `json:"zip_files,omitempty"` + FolderPath string `json:"folder_path,omitempty"` + Title string `json:"title,omitempty"` + Code string `json:"code,omitempty"` + URLs []string `json:"urls,omitempty"` + Date string `json:"date,omitempty"` + Details string `json:"details,omitempty"` + Photographer string `json:"photographer,omitempty"` + Rating int `json:"rating,omitempty"` + Organized bool `json:"organized,omitempty"` + Chapters []GalleryChapter `json:"chapters,omitempty"` + Studio string `json:"studio,omitempty"` + Performers []string `json:"performers,omitempty"` + Tags []string `json:"tags,omitempty"` + CreatedAt json.JSONTime `json:"created_at,omitempty"` + UpdatedAt json.JSONTime `json:"updated_at,omitempty"` // deprecated - for import only URL string `json:"url,omitempty"` diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index d35b9a360..4b6a3183d 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -10,9 +10,11 @@ import ( type Gallery struct { ID int `json:"id"` - Title string `json:"title"` - Date *Date `json:"date"` - Details string `json:"details"` + Title string `json:"title"` + Code string `json:"code"` + Date *Date `json:"date"` + Details string `json:"details"` + Photographer string `json:"photographer"` // Rating expressed in 1-100 scale Rating *int `json:"rating"` Organized bool `json:"organized"` @@ -50,10 +52,12 @@ type GalleryPartial struct { // Path OptionalString // Checksum OptionalString // Zip OptionalBool - Title OptionalString - URLs *UpdateStrings - Date OptionalDate - Details OptionalString + Title OptionalString + Code OptionalString + URLs *UpdateStrings + Date OptionalDate + Details OptionalString + Photographer OptionalString // Rating expressed in 1-100 scale Rating OptionalInt Organized OptionalBool diff --git a/pkg/scraper/gallery.go b/pkg/scraper/gallery.go index db316409b..e0b78df45 100644 --- a/pkg/scraper/gallery.go +++ b/pkg/scraper/gallery.go @@ -3,13 +3,15 @@ package scraper import "github.com/stashapp/stash/pkg/models" type ScrapedGallery struct { - Title *string `json:"title"` - Details *string `json:"details"` - URLs []string `json:"urls"` - Date *string `json:"date"` - Studio *models.ScrapedStudio `json:"studio"` - Tags []*models.ScrapedTag `json:"tags"` - Performers []*models.ScrapedPerformer `json:"performers"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Photographer *string `json:"photographer"` + URLs []string `json:"urls"` + Date *string `json:"date"` + Studio *models.ScrapedStudio `json:"studio"` + Tags []*models.ScrapedTag `json:"tags"` + Performers []*models.ScrapedPerformer `json:"performers"` // deprecated URL *string `json:"url"` @@ -18,10 +20,12 @@ type ScrapedGallery struct { func (ScrapedGallery) IsScrapedContent() {} type ScrapedGalleryInput struct { - Title *string `json:"title"` - Details *string `json:"details"` - URLs []string `json:"urls"` - Date *string `json:"date"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Photographer *string `json:"photographer"` + URLs []string `json:"urls"` + Date *string `json:"date"` // deprecated URL *string `json:"url"` diff --git a/pkg/scraper/mapped.go b/pkg/scraper/mapped.go index 2a44e30d6..3d4ee4638 100644 --- a/pkg/scraper/mapped.go +++ b/pkg/scraper/mapped.go @@ -972,11 +972,12 @@ func (s mappedScraper) scrapeScenes(ctx context.Context, q mappedQuery) ([]*Scra func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*ScrapedScene, error) { sceneScraperConfig := s.Scene - sceneMap := sceneScraperConfig.mappedConfig - if sceneMap == nil { + if sceneScraperConfig == nil { return nil, nil } + sceneMap := sceneScraperConfig.mappedConfig + logger.Debug(`Processing scene:`) results := sceneMap.process(ctx, q, s.Common) @@ -1000,11 +1001,12 @@ func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*Scrap var ret ScrapedGallery galleryScraperConfig := s.Gallery - galleryMap := galleryScraperConfig.mappedConfig - if galleryMap == nil { + if galleryScraperConfig == nil { return nil, nil } + galleryMap := galleryScraperConfig.mappedConfig + galleryPerformersMap := galleryScraperConfig.Performers galleryTagsMap := galleryScraperConfig.Tags galleryStudioMap := galleryScraperConfig.Studio @@ -1062,11 +1064,12 @@ func (s mappedScraper) scrapeMovie(ctx context.Context, q mappedQuery) (*models. var ret models.ScrapedMovie movieScraperConfig := s.Movie - movieMap := movieScraperConfig.mappedConfig - if movieMap == nil { + if movieScraperConfig == nil { return nil, nil } + movieMap := movieScraperConfig.mappedConfig + movieStudioMap := movieScraperConfig.Studio results := movieMap.process(ctx, q, s.Common) diff --git a/pkg/sqlite/database.go b/pkg/sqlite/database.go index 36e6fc206..727c12389 100644 --- a/pkg/sqlite/database.go +++ b/pkg/sqlite/database.go @@ -33,7 +33,7 @@ const ( dbConnTimeout = 30 ) -var appSchemaVersion uint = 52 +var appSchemaVersion uint = 53 //go:embed migrations/*.sql var migrationsBox embed.FS diff --git a/pkg/sqlite/gallery.go b/pkg/sqlite/gallery.go index a305b1b25..1c9e1cf7e 100644 --- a/pkg/sqlite/gallery.go +++ b/pkg/sqlite/gallery.go @@ -31,10 +31,12 @@ const ( ) type galleryRow struct { - ID int `db:"id" goqu:"skipinsert"` - Title zero.String `db:"title"` - Date NullDate `db:"date"` - Details zero.String `db:"details"` + ID int `db:"id" goqu:"skipinsert"` + Title zero.String `db:"title"` + Code zero.String `db:"code"` + Date NullDate `db:"date"` + Details zero.String `db:"details"` + Photographer zero.String `db:"photographer"` // expressed as 1-100 Rating null.Int `db:"rating"` Organized bool `db:"organized"` @@ -47,8 +49,10 @@ type galleryRow struct { func (r *galleryRow) fromGallery(o models.Gallery) { r.ID = o.ID r.Title = zero.StringFrom(o.Title) + r.Code = zero.StringFrom(o.Code) r.Date = NullDateFromDatePtr(o.Date) r.Details = zero.StringFrom(o.Details) + r.Photographer = zero.StringFrom(o.Photographer) r.Rating = intFromPtr(o.Rating) r.Organized = o.Organized r.StudioID = intFromPtr(o.StudioID) @@ -70,8 +74,10 @@ func (r *galleryQueryRow) resolve() *models.Gallery { ret := &models.Gallery{ ID: r.ID, Title: r.Title.String, + Code: r.Code.String, Date: r.Date.DatePtr(), Details: r.Details.String, + Photographer: r.Photographer.String, Rating: nullIntPtr(r.Rating), Organized: r.Organized, StudioID: nullIntPtr(r.StudioID), @@ -96,8 +102,10 @@ type galleryRowRecord struct { func (r *galleryRowRecord) fromPartial(o models.GalleryPartial) { r.setNullString("title", o.Title) + r.setNullString("code", o.Code) r.setNullDate("date", o.Date) r.setNullString("details", o.Details) + r.setNullString("photographer", o.Photographer) r.setNullInt("rating", o.Rating) r.setBool("organized", o.Organized) r.setNullInt("studio_id", o.StudioID) @@ -655,7 +663,9 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga query.handleCriterion(ctx, intCriterionHandler(galleryFilter.ID, "galleries.id", nil)) query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Title, "galleries.title")) + query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Code, "galleries.code")) query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Details, "galleries.details")) + query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Photographer, "galleries.photographer")) query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) { if galleryFilter.Checksum != nil { diff --git a/pkg/sqlite/gallery_test.go b/pkg/sqlite/gallery_test.go index 5a7c5088c..c57ba08b8 100644 --- a/pkg/sqlite/gallery_test.go +++ b/pkg/sqlite/gallery_test.go @@ -56,12 +56,14 @@ func loadGalleryRelationships(ctx context.Context, expected models.Gallery, actu func Test_galleryQueryBuilder_Create(t *testing.T) { var ( - title = "title" - url = "url" - rating = 60 - details = "details" - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "1337" + url = "url" + rating = 60 + details = "details" + photographer = "photographer" + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) galleryFile = makeFileWithID(fileIdxStartGalleryFiles) ) @@ -77,9 +79,11 @@ func Test_galleryQueryBuilder_Create(t *testing.T) { "full", models.Gallery{ Title: title, + Code: code, URLs: models.NewRelatedStrings([]string{url}), Date: &date, Details: details, + Photographer: photographer, Rating: &rating, Organized: true, StudioID: &studioIDs[studioIdxWithScene], @@ -94,13 +98,15 @@ func Test_galleryQueryBuilder_Create(t *testing.T) { { "with file", models.Gallery{ - Title: title, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Details: details, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], + Title: title, + Code: code, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + Details: details, + Photographer: photographer, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], Files: models.NewRelatedFiles([]models.File{ galleryFile, }), @@ -207,12 +213,14 @@ func makeGalleryFileWithID(i int) *models.BaseFile { func Test_galleryQueryBuilder_Update(t *testing.T) { var ( - title = "title" - url = "url" - rating = 60 - details = "details" - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "code" + url = "url" + rating = 60 + details = "details" + photographer = "photographer" + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) ) date, _ := models.ParseDate("2003-02-01") @@ -225,14 +233,16 @@ func Test_galleryQueryBuilder_Update(t *testing.T) { { "full", &models.Gallery{ - ID: galleryIDs[galleryIdxWithScene], - Title: title, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Details: details, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], + ID: galleryIDs[galleryIdxWithScene], + Title: title, + Code: code, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + Details: details, + Photographer: photographer, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], Files: models.NewRelatedFiles([]models.File{ makeGalleryFileWithID(galleryIdxWithScene), }), @@ -389,7 +399,9 @@ func clearGalleryPartial() models.GalleryPartial { // leave mandatory fields return models.GalleryPartial{ Title: models.OptionalString{Set: true, Null: true}, + Code: models.OptionalString{Set: true, Null: true}, Details: models.OptionalString{Set: true, Null: true}, + Photographer: models.OptionalString{Set: true, Null: true}, URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet}, Date: models.OptionalDate{Set: true, Null: true}, Rating: models.OptionalInt{Set: true, Null: true}, @@ -401,12 +413,14 @@ func clearGalleryPartial() models.GalleryPartial { func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) { var ( - title = "title" - details = "details" - url = "url" - rating = 60 - createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) - updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + title = "title" + code = "code" + details = "details" + photographer = "photographer" + url = "url" + rating = 60 + createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) date, _ = models.ParseDate("2003-02-01") ) @@ -422,8 +436,10 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) { "full", galleryIDs[galleryIdxWithImage], models.GalleryPartial{ - Title: models.NewOptionalString(title), - Details: models.NewOptionalString(details), + Title: models.NewOptionalString(title), + Code: models.NewOptionalString(code), + Details: models.NewOptionalString(details), + Photographer: models.NewOptionalString(photographer), URLs: &models.UpdateStrings{ Values: []string{url}, Mode: models.RelationshipUpdateModeSet, @@ -449,14 +465,16 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) { }, }, models.Gallery{ - ID: galleryIDs[galleryIdxWithImage], - Title: title, - Details: details, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithGallery], + ID: galleryIDs[galleryIdxWithImage], + Title: title, + Code: code, + Details: details, + Photographer: photographer, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithGallery], Files: models.NewRelatedFiles([]models.File{ makeGalleryFile(galleryIdxWithImage), }), diff --git a/pkg/sqlite/migrations/53_gallery_photographer_code.up.sql b/pkg/sqlite/migrations/53_gallery_photographer_code.up.sql new file mode 100644 index 000000000..a119fc18b --- /dev/null +++ b/pkg/sqlite/migrations/53_gallery_photographer_code.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE `galleries` ADD COLUMN `code` text; +ALTER TABLE `galleries` ADD COLUMN `photographer` text; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx index 83ffe2bc3..b80c281dd 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx @@ -24,7 +24,7 @@ export const GalleryDetailPanel: React.FC = ({ return ( <>
- + :{" "}

{gallery.details}

@@ -111,6 +111,16 @@ export const GalleryDetailPanel: React.FC = ({ :{" "} {TextUtils.formatDateTime(intl, gallery.updated_at)}{" "} + {gallery.code && ( +
+ : {gallery.code}{" "} +
+ )} + {gallery.photographer && ( +
+ : {gallery.photographer}{" "} +
+ )} {gallery.studio && (
diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx index 30c5e41fc..cad23d5c8 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -86,8 +86,10 @@ export const GalleryEditPanel: React.FC = ({ const schema = yup.object({ title: titleRequired ? yup.string().required() : yup.string().ensure(), + code: yup.string().ensure(), urls: yupUniqueStringList("urls"), date: yupDateString(intl), + photographer: yup.string().ensure(), rating100: yup.number().integer().nullable().defined(), studio_id: yup.string().required().nullable(), performer_ids: yup.array(yup.string().required()).defined(), @@ -98,8 +100,10 @@ export const GalleryEditPanel: React.FC = ({ const initialValues = { title: gallery?.title ?? "", + code: gallery?.code ?? "", urls: gallery?.urls ?? [], date: gallery?.date ?? "", + photographer: gallery?.photographer ?? "", rating100: gallery?.rating100 ?? null, studio_id: gallery?.studio?.id ?? null, performer_ids: (gallery?.performers ?? []).map((p) => p.id), @@ -288,10 +292,18 @@ export const GalleryEditPanel: React.FC = ({ formik.setFieldValue("title", galleryData.title); } + if (galleryData.code) { + formik.setFieldValue("code", galleryData.code); + } + if (galleryData.details) { formik.setFieldValue("details", galleryData.details); } + if (galleryData.photographer) { + formik.setFieldValue("photographer", galleryData.photographer); + } + if (galleryData.date) { formik.setFieldValue("date", galleryData.date); } @@ -490,6 +502,7 @@ export const GalleryEditPanel: React.FC = ({ {renderInputField("title")} + {renderInputField("code", "text", "scene_code")} {renderURLListField( "urls", @@ -499,6 +512,7 @@ export const GalleryEditPanel: React.FC = ({ )} {renderDateField("date")} + {renderInputField("photographer")} {renderRatingField("rating100", "rating")} {renderScenesField()} diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx index 8cfc9dbf2..c9c807816 100644 --- a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx @@ -48,6 +48,9 @@ export const GalleryScrapeDialog: React.FC = ({ const [title, setTitle] = useState>( new ScrapeResult(gallery.title, scraped.title) ); + const [code, setCode] = useState>( + new ScrapeResult(gallery.code, scraped.code) + ); const [urls, setURLs] = useState>( new ScrapeResult( gallery.urls, @@ -59,6 +62,9 @@ export const GalleryScrapeDialog: React.FC = ({ const [date, setDate] = useState>( new ScrapeResult(gallery.date, scraped.date) ); + const [photographer, setPhotographer] = useState>( + new ScrapeResult(gallery.photographer, scraped.photographer) + ); const [studio, setStudio] = useState>( new ScrapeResult(gallery.studio_id, scraped.studio?.stored_id) ); @@ -157,9 +163,17 @@ export const GalleryScrapeDialog: React.FC = ({ // don't show the dialog if nothing was scraped if ( - [title, urls, date, studio, performers, tags, details].every( - (r) => !r.scraped - ) && + [ + title, + code, + urls, + date, + photographer, + studio, + performers, + tags, + details, + ].every((r) => !r.scraped) && !newStudio && newPerformers.length === 0 && newTags.length === 0 @@ -173,8 +187,10 @@ export const GalleryScrapeDialog: React.FC = ({ return { title: title.getNewValue(), + code: code.getNewValue(), urls: urls.getNewValue(), date: date.getNewValue(), + photographer: photographer.getNewValue(), studio: newStudioValue ? { stored_id: newStudioValue, @@ -200,6 +216,11 @@ export const GalleryScrapeDialog: React.FC = ({ result={title} onChange={(value) => setTitle(value)} /> + setCode(value)} + /> = ({ result={date} onChange={(value) => setDate(value)} /> + setPhotographer(value)} + />