diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index c7d880266..07a997ee0 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -353,6 +353,8 @@ input SceneFilterType { last_played_at: TimestampCriterionInput "Filter by date" date: DateCriterionInput + "Filter by production date" + production_date: DateCriterionInput "Filter by creation time" created_at: TimestampCriterionInput "Filter by last update time" diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 4d99e0a21..140a8e57e 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -45,6 +45,7 @@ type Scene { url: String @deprecated(reason: "Use urls") urls: [String!]! date: String + production_date: String # rating expressed as 1-100 rating100: Int organized: Boolean! @@ -103,6 +104,7 @@ input SceneCreateInput { url: String @deprecated(reason: "Use urls") urls: [String!] date: String + production_date: String # rating expressed as 1-100 rating100: Int organized: Boolean @@ -136,6 +138,7 @@ input SceneUpdateInput { url: String @deprecated(reason: "Use urls") urls: [String!] date: String + production_date: String # rating expressed as 1-100 rating100: Int o_counter: Int @@ -187,6 +190,7 @@ input BulkSceneUpdateInput { url: String @deprecated(reason: "Use urls") urls: BulkUpdateStrings date: String + production_date: String # rating expressed as 1-100 rating100: Int organized: Boolean @@ -245,6 +249,7 @@ type SceneParserResult { director: String url: String date: String + production_date: String # rating expressed as 1-5 rating: Int @deprecated(reason: "Use 1-100 range with rating100") # rating expressed as 1-100 diff --git a/graphql/schema/types/scraper.graphql b/graphql/schema/types/scraper.graphql index fafd928f7..41bd32486 100644 --- a/graphql/schema/types/scraper.graphql +++ b/graphql/schema/types/scraper.graphql @@ -86,6 +86,7 @@ type ScrapedScene { url: String @deprecated(reason: "use urls") urls: [String!] date: String + production_date: String "This should be a base64 encoded data URL" image: String @@ -110,6 +111,7 @@ input ScrapedSceneInput { url: String @deprecated(reason: "use urls") urls: [String!] date: String + production_date: String # no image, file, duration or relationships diff --git a/graphql/stash-box/query.graphql b/graphql/stash-box/query.graphql index ebaf05648..cc04f0595 100644 --- a/graphql/stash-box/query.graphql +++ b/graphql/stash-box/query.graphql @@ -107,6 +107,7 @@ fragment SceneFragment on Scene { director duration date + production_date urls { ...URLFragment } diff --git a/internal/api/resolver_model_scene.go b/internal/api/resolver_model_scene.go index ecb163765..c4576edfb 100644 --- a/internal/api/resolver_model_scene.go +++ b/internal/api/resolver_model_scene.go @@ -74,6 +74,14 @@ func (r *sceneResolver) Date(ctx context.Context, obj *models.Scene) (*string, e return nil, nil } +func (r *sceneResolver) ProductionDate(ctx context.Context, obj *models.Scene) (*string, error) { + if obj.ProductionDate != nil { + result := obj.ProductionDate.String() + return &result, nil + } + return nil, nil +} + func (r *sceneResolver) Files(ctx context.Context, obj *models.Scene) ([]*VideoFile, error) { files, err := r.getFiles(ctx, obj) if err != nil { diff --git a/internal/api/resolver_mutation_scene.go b/internal/api/resolver_mutation_scene.go index 70158fc6f..5b87f740f 100644 --- a/internal/api/resolver_mutation_scene.go +++ b/internal/api/resolver_mutation_scene.go @@ -57,6 +57,10 @@ func (r *mutationResolver) SceneCreate(ctx context.Context, input models.SceneCr if err != nil { return nil, fmt.Errorf("converting date: %w", err) } + newScene.ProductionDate, err = translator.datePtr(input.ProductionDate) + if err != nil { + return nil, fmt.Errorf("converting production date: %w", err) + } newScene.StudioID, err = translator.intPtrFromString(input.StudioID) if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) @@ -207,6 +211,10 @@ func scenePartialFromInput(input models.SceneUpdateInput, translator changesetTr if err != nil { return nil, fmt.Errorf("converting date: %w", err) } + updatedScene.ProductionDate, err = translator.optionalDate(input.ProductionDate, "production_date") + if err != nil { + return nil, fmt.Errorf("converting production date: %w", err) + } updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) @@ -377,6 +385,10 @@ func (r *mutationResolver) BulkSceneUpdate(ctx context.Context, input BulkSceneU if err != nil { return nil, fmt.Errorf("converting date: %w", err) } + updatedScene.ProductionDate, err = translator.optionalDate(input.ProductionDate, "production_date") + if err != nil { + return nil, fmt.Errorf("converting production date: %w", err) + } updatedScene.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id") if err != nil { return nil, fmt.Errorf("converting studio id: %w", err) diff --git a/pkg/models/filename_parser.go b/pkg/models/filename_parser.go index 584ae72cb..dafa768c7 100644 --- a/pkg/models/filename_parser.go +++ b/pkg/models/filename_parser.go @@ -8,20 +8,21 @@ type SceneParserInput struct { } type SceneParserResult struct { - Scene *Scene `json:"scene"` - Title *string `json:"title"` - Code *string `json:"code"` - Details *string `json:"details"` - Director *string `json:"director"` - URL *string `json:"url"` - Date *string `json:"date"` - Rating *int `json:"rating"` - Rating100 *int `json:"rating100"` - StudioID *string `json:"studio_id"` - GalleryIds []string `json:"gallery_ids"` - PerformerIds []string `json:"performer_ids"` - Movies []*SceneMovieID `json:"movies"` - TagIds []string `json:"tag_ids"` + Scene *Scene `json:"scene"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + Date *string `json:"date"` + ProductionDate *string `json:"production_date"` + Rating *int `json:"rating"` + Rating100 *int `json:"rating100"` + StudioID *string `json:"studio_id"` + GalleryIds []string `json:"gallery_ids"` + PerformerIds []string `json:"performer_ids"` + Movies []*SceneMovieID `json:"movies"` + TagIds []string `json:"tag_ids"` } type SceneMovieID struct { diff --git a/pkg/models/jsonschema/scene.go b/pkg/models/jsonschema/scene.go index 8f15b9c5d..158f0cd6f 100644 --- a/pkg/models/jsonschema/scene.go +++ b/pkg/models/jsonschema/scene.go @@ -47,10 +47,11 @@ type Scene struct { // deprecated - for import only URL string `json:"url,omitempty"` - URLs []string `json:"urls,omitempty"` - Date string `json:"date,omitempty"` - Rating int `json:"rating,omitempty"` - Organized bool `json:"organized,omitempty"` + URLs []string `json:"urls,omitempty"` + Date string `json:"date,omitempty"` + ProductionDate string `json:"production_date,omitempty"` + Rating int `json:"rating,omitempty"` + Organized bool `json:"organized,omitempty"` // deprecated - for import only OCounter int `json:"o_counter,omitempty"` diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index 64ad34b9c..d51e58c85 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -10,12 +10,13 @@ import ( // Scene stores the metadata for a single video scene. type Scene struct { - ID int `json:"id"` - Title string `json:"title"` - Code string `json:"code"` - Details string `json:"details"` - Director string `json:"director"` - Date *Date `json:"date"` + ID int `json:"id"` + Title string `json:"title"` + Code string `json:"code"` + Details string `json:"details"` + Director string `json:"director"` + Date *Date `json:"date"` + ProductionDate *Date `json:"production_date"` // Rating expressed in 1-100 scale Rating *int `json:"rating"` Organized bool `json:"organized"` @@ -70,11 +71,12 @@ type UpdateSceneInput struct { // ScenePartial represents part of a Scene object. It is used to update // the database entry. type ScenePartial struct { - Title OptionalString - Code OptionalString - Details OptionalString - Director OptionalString - Date OptionalDate + Title OptionalString + Code OptionalString + Details OptionalString + Director OptionalString + Date OptionalDate + ProductionDate OptionalDate // Rating expressed in 1-100 scale Rating OptionalInt Organized OptionalBool @@ -206,27 +208,35 @@ func (s ScenePartial) UpdateInput(id int) SceneUpdateInput { dateStr = &v } + var productionDateStr *string + if s.ProductionDate.Set { + d := s.ProductionDate.Value + v := d.String() + productionDateStr = &v + } + var stashIDs StashIDs if s.StashIDs != nil { stashIDs = StashIDs(s.StashIDs.StashIDs) } ret := SceneUpdateInput{ - ID: strconv.Itoa(id), - Title: s.Title.Ptr(), - Code: s.Code.Ptr(), - Details: s.Details.Ptr(), - Director: s.Director.Ptr(), - Urls: s.URLs.Strings(), - Date: dateStr, - Rating100: s.Rating.Ptr(), - Organized: s.Organized.Ptr(), - StudioID: s.StudioID.StringPtr(), - GalleryIds: s.GalleryIDs.IDStrings(), - PerformerIds: s.PerformerIDs.IDStrings(), - Movies: s.GroupIDs.SceneMovieInputs(), - TagIds: s.TagIDs.IDStrings(), - StashIds: stashIDs.ToStashIDInputs(), + ID: strconv.Itoa(id), + Title: s.Title.Ptr(), + Code: s.Code.Ptr(), + Details: s.Details.Ptr(), + Director: s.Director.Ptr(), + Urls: s.URLs.Strings(), + Date: dateStr, + ProductionDate: productionDateStr, + Rating100: s.Rating.Ptr(), + Organized: s.Organized.Ptr(), + StudioID: s.StudioID.StringPtr(), + GalleryIds: s.GalleryIDs.IDStrings(), + PerformerIds: s.PerformerIDs.IDStrings(), + Movies: s.GroupIDs.SceneMovieInputs(), + TagIds: s.TagIDs.IDStrings(), + StashIds: stashIDs.ToStashIDInputs(), } return ret diff --git a/pkg/models/model_scene_test.go b/pkg/models/model_scene_test.go index 4eb5c1833..5c9468d58 100644 --- a/pkg/models/model_scene_test.go +++ b/pkg/models/model_scene_test.go @@ -12,19 +12,21 @@ func TestScenePartial_UpdateInput(t *testing.T) { ) var ( - title = "title" - code = "1337" - details = "details" - director = "director" - url = "url" - date = "2001-02-03" - rating100 = 80 - organized = true - studioID = 2 - studioIDStr = "2" + title = "title" + code = "1337" + details = "details" + director = "director" + url = "url" + date = "2001-02-03" + productionDate = "2001-01-02" + rating100 = 80 + organized = true + studioID = 2 + studioIDStr = "2" ) dateObj, _ := ParseDate(date) + productionDateObj, _ := ParseDate(productionDate) tests := []struct { name string @@ -44,22 +46,24 @@ func TestScenePartial_UpdateInput(t *testing.T) { Values: []string{url}, Mode: RelationshipUpdateModeSet, }, - Date: NewOptionalDate(dateObj), - Rating: NewOptionalInt(rating100), - Organized: NewOptionalBool(organized), - StudioID: NewOptionalInt(studioID), + Date: NewOptionalDate(dateObj), + ProductionDate: NewOptionalDate(productionDateObj), + Rating: NewOptionalInt(rating100), + Organized: NewOptionalBool(organized), + StudioID: NewOptionalInt(studioID), }, SceneUpdateInput{ - ID: idStr, - Title: &title, - Code: &code, - Details: &details, - Director: &director, - Urls: []string{url}, - Date: &date, - Rating100: &rating100, - Organized: &organized, - StudioID: &studioIDStr, + ID: idStr, + Title: &title, + Code: &code, + Details: &details, + Director: &director, + Urls: []string{url}, + Date: &date, + ProductionDate: &productionDate, + Rating100: &rating100, + Organized: &organized, + StudioID: &studioIDStr, }, }, { diff --git a/pkg/models/model_scraped_item.go b/pkg/models/model_scraped_item.go index d20fbd589..000ece9c0 100644 --- a/pkg/models/model_scraped_item.go +++ b/pkg/models/model_scraped_item.go @@ -663,13 +663,14 @@ func (g ScrapedGroup) ScrapedMovie() ScrapedMovie { } type ScrapedScene struct { - Title *string `json:"title"` - Code *string `json:"code"` - Details *string `json:"details"` - Director *string `json:"director"` - URL *string `json:"url"` - URLs []string `json:"urls"` - Date *string `json:"date"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + URLs []string `json:"urls"` + Date *string `json:"date"` + ProductionDate *string `json:"production_date"` // This should be a base64 encoded data URL Image *string `json:"image"` File *SceneFileType `json:"file"` @@ -686,14 +687,15 @@ type ScrapedScene struct { func (ScrapedScene) IsScrapedContent() {} type ScrapedSceneInput struct { - Title *string `json:"title"` - Code *string `json:"code"` - Details *string `json:"details"` - Director *string `json:"director"` - URL *string `json:"url"` - URLs []string `json:"urls"` - Date *string `json:"date"` - RemoteSiteID *string `json:"remote_site_id"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + URLs []string `json:"urls"` + Date *string `json:"date"` + ProductionDate *string `json:"production_date"` + RemoteSiteID *string `json:"remote_site_id"` } type ScrapedImage struct { diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 839452501..41ac710a9 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -119,6 +119,8 @@ type SceneFilterType struct { LastPlayedAt *TimestampCriterionInput `json:"last_played_at"` // Filter by date Date *DateCriterionInput `json:"date"` + // Filter by production date + ProductionDate *DateCriterionInput `json:"production_date"` // Filter by related galleries that meet this criteria GalleriesFilter *GalleryFilterType `json:"galleries_filter"` // Filter by related performers that meet this criteria @@ -174,21 +176,22 @@ type SceneGroupInput struct { } type SceneCreateInput struct { - Title *string `json:"title"` - Code *string `json:"code"` - Details *string `json:"details"` - Director *string `json:"director"` - URL *string `json:"url"` - Urls []string `json:"urls"` - Date *string `json:"date"` - Rating100 *int `json:"rating100"` - Organized *bool `json:"organized"` - StudioID *string `json:"studio_id"` - GalleryIds []string `json:"gallery_ids"` - PerformerIds []string `json:"performer_ids"` - Movies []SceneMovieInput `json:"movies"` - Groups []SceneGroupInput `json:"groups"` - TagIds []string `json:"tag_ids"` + Title *string `json:"title"` + Code *string `json:"code"` + Details *string `json:"details"` + Director *string `json:"director"` + URL *string `json:"url"` + Urls []string `json:"urls"` + Date *string `json:"date"` + ProductionDate *string `json:"production_date"` + Rating100 *int `json:"rating100"` + Organized *bool `json:"organized"` + StudioID *string `json:"studio_id"` + GalleryIds []string `json:"gallery_ids"` + PerformerIds []string `json:"performer_ids"` + Movies []SceneMovieInput `json:"movies"` + Groups []SceneGroupInput `json:"groups"` + TagIds []string `json:"tag_ids"` // This should be a URL or a base64 encoded data URL CoverImage *string `json:"cover_image"` StashIds []StashIDInput `json:"stash_ids"` @@ -209,6 +212,7 @@ type SceneUpdateInput struct { URL *string `json:"url"` Urls []string `json:"urls"` Date *string `json:"date"` + ProductionDate *string `json:"production_date"` Rating100 *int `json:"rating100"` OCounter *int `json:"o_counter"` Organized *bool `json:"organized"` diff --git a/pkg/scene/export.go b/pkg/scene/export.go index 069bd587f..07ab3579d 100644 --- a/pkg/scene/export.go +++ b/pkg/scene/export.go @@ -45,6 +45,10 @@ func ToBasicJSON(ctx context.Context, reader ExportGetter, scene *models.Scene) newSceneJSON.Date = scene.Date.String() } + if scene.ProductionDate != nil { + newSceneJSON.ProductionDate = scene.ProductionDate.String() + } + if scene.Rating != nil { newSceneJSON.Rating = *scene.Rating } diff --git a/pkg/scene/import.go b/pkg/scene/import.go index 24dbf1cc0..74b65ee9c 100644 --- a/pkg/scene/import.go +++ b/pkg/scene/import.go @@ -110,6 +110,12 @@ func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene { newScene.Date = &d } } + if sceneJSON.ProductionDate != "" { + d, err := models.ParseDate(sceneJSON.ProductionDate) + if err == nil { + newScene.ProductionDate = &d + } + } if sceneJSON.Rating != 0 { newScene.Rating = &sceneJSON.Rating } diff --git a/pkg/scraper/query_url.go b/pkg/scraper/query_url.go index 7fe874947..fb88bfd2e 100644 --- a/pkg/scraper/query_url.go +++ b/pkg/scraper/query_url.go @@ -49,6 +49,7 @@ func queryURLParametersFromScrapedScene(scene models.ScrapedSceneInput) queryURL setField("url", scene.URL) } setField("date", scene.Date) + setField("production_date", scene.ProductionDate) setField("details", scene.Details) setField("director", scene.Director) setField("remote_site_id", scene.RemoteSiteID) diff --git a/pkg/scraper/stash.go b/pkg/scraper/stash.go index 23c4b9063..c716b6a96 100644 --- a/pkg/scraper/stash.go +++ b/pkg/scraper/stash.go @@ -328,15 +328,16 @@ func (f stashVideoFile) SceneFileType() models.SceneFileType { } type scrapedSceneStash struct { - ID string `graphql:"id" json:"id"` - Title *string `graphql:"title" json:"title"` - Details *string `graphql:"details" json:"details"` - URLs []string `graphql:"urls" json:"urls"` - Date *string `graphql:"date" json:"date"` - Files []stashVideoFile `graphql:"files" json:"files"` - Studio *scrapedStudioStash `graphql:"studio" json:"studio"` - Tags []*scrapedTagStash `graphql:"tags" json:"tags"` - Performers []*scrapedPerformerStash `graphql:"performers" json:"performers"` + ID string `graphql:"id" json:"id"` + Title *string `graphql:"title" json:"title"` + Details *string `graphql:"details" json:"details"` + URLs []string `graphql:"urls" json:"urls"` + Date *string `graphql:"date" json:"date"` + ProductionDate *string `graphql:"production_date" json:"production_date"` + Files []stashVideoFile `graphql:"files" json:"files"` + Studio *scrapedStudioStash `graphql:"studio" json:"studio"` + Tags []*scrapedTagStash `graphql:"tags" json:"tags"` + Performers []*scrapedPerformerStash `graphql:"performers" json:"performers"` } func (s *stashScraper) scrapeSceneByScene(ctx context.Context, scene *models.Scene) (*models.ScrapedScene, error) { diff --git a/pkg/sqlite/database.go b/pkg/sqlite/database.go index 7c383dc4c..026a18c08 100644 --- a/pkg/sqlite/database.go +++ b/pkg/sqlite/database.go @@ -34,7 +34,7 @@ const ( cacheSizeEnv = "STASH_SQLITE_CACHE_SIZE" ) -var appSchemaVersion uint = 85 +var appSchemaVersion uint = 86 //go:embed migrations/*.sql var migrationsBox embed.FS diff --git a/pkg/sqlite/migrations/86_scene_production_date.up.sql b/pkg/sqlite/migrations/86_scene_production_date.up.sql new file mode 100644 index 000000000..f788b1a6b --- /dev/null +++ b/pkg/sqlite/migrations/86_scene_production_date.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE `scenes` ADD COLUMN `production_date` date; +ALTER TABLE `scenes` ADD COLUMN `production_date_precision` TINYINT; \ No newline at end of file diff --git a/pkg/sqlite/scene.go b/pkg/sqlite/scene.go index c2093431d..630db5a78 100644 --- a/pkg/sqlite/scene.go +++ b/pkg/sqlite/scene.go @@ -77,13 +77,15 @@ ORDER BY files.size DESC; ` type sceneRow struct { - ID int `db:"id" goqu:"skipinsert"` - Title zero.String `db:"title"` - Code zero.String `db:"code"` - Details zero.String `db:"details"` - Director zero.String `db:"director"` - Date NullDate `db:"date"` - DatePrecision null.Int `db:"date_precision"` + ID int `db:"id" goqu:"skipinsert"` + Title zero.String `db:"title"` + Code zero.String `db:"code"` + Details zero.String `db:"details"` + Director zero.String `db:"director"` + Date NullDate `db:"date"` + DatePrecision null.Int `db:"date_precision"` + ProductionDate NullDate `db:"production_date"` + ProductionDatePrecision null.Int `db:"production_date_precision"` // expressed as 1-100 Rating null.Int `db:"rating"` Organized bool `db:"organized"` @@ -105,6 +107,8 @@ func (r *sceneRow) fromScene(o models.Scene) { r.Director = zero.StringFrom(o.Director) r.Date = NullDateFromDatePtr(o.Date) r.DatePrecision = datePrecisionFromDatePtr(o.Date) + r.ProductionDate = NullDateFromDatePtr(o.ProductionDate) + r.ProductionDatePrecision = datePrecisionFromDatePtr(o.ProductionDate) r.Rating = intFromPtr(o.Rating) r.Organized = o.Organized r.StudioID = intFromPtr(o.StudioID) @@ -125,15 +129,16 @@ type sceneQueryRow struct { func (r *sceneQueryRow) resolve() *models.Scene { ret := &models.Scene{ - ID: r.ID, - Title: r.Title.String, - Code: r.Code.String, - Details: r.Details.String, - Director: r.Director.String, - Date: r.Date.DatePtr(r.DatePrecision), - Rating: nullIntPtr(r.Rating), - Organized: r.Organized, - StudioID: nullIntPtr(r.StudioID), + ID: r.ID, + Title: r.Title.String, + Code: r.Code.String, + Details: r.Details.String, + Director: r.Director.String, + Date: r.Date.DatePtr(r.DatePrecision), + ProductionDate: r.ProductionDate.DatePtr(r.ProductionDatePrecision), + Rating: nullIntPtr(r.Rating), + Organized: r.Organized, + StudioID: nullIntPtr(r.StudioID), PrimaryFileID: nullIntFileIDPtr(r.PrimaryFileID), OSHash: r.PrimaryFileOshash.String, @@ -163,6 +168,7 @@ func (r *sceneRowRecord) fromPartial(o models.ScenePartial) { r.setNullString("details", o.Details) r.setNullString("director", o.Director) r.setNullDate("date", "date_precision", o.Date) + r.setNullDate("production_date", "production_date_precision", o.ProductionDate) r.setNullInt("rating", o.Rating) r.setBool("organized", o.Organized) r.setNullInt("studio_id", o.StudioID) @@ -1122,6 +1128,7 @@ var sceneSortOptions = sortOptions{ "created_at", "code", "date", + "production_date", "file_count", "filesize", "duration", diff --git a/pkg/sqlite/scene_filter.go b/pkg/sqlite/scene_filter.go index 255a8e0b3..c013ba8bb 100644 --- a/pkg/sqlite/scene_filter.go +++ b/pkg/sqlite/scene_filter.go @@ -189,6 +189,7 @@ func (qb *sceneFilterHandler) criterionHandler() criterionHandler { qb.performerAgeCriterionHandler(sceneFilter.PerformerAge), qb.duplicatedCriterionHandler(sceneFilter.Duplicated), &dateCriterionHandler{sceneFilter.Date, "scenes.date", nil}, + &dateCriterionHandler{sceneFilter.ProductionDate, "scenes.production_date", nil}, ×tampCriterionHandler{sceneFilter.CreatedAt, "scenes.created_at", nil}, ×tampCriterionHandler{sceneFilter.UpdatedAt, "scenes.updated_at", nil}, @@ -430,6 +431,8 @@ func (qb *sceneFilterHandler) isMissingCriterionHandler(isMissing *string) crite f.addWhere("performers_join.scene_id IS NULL") case "date": f.addWhere(`scenes.date IS NULL OR scenes.date IS ""`) + case "production_date": + f.addWhere(`scenes.production_date IS NULL OR scenes.production_date IS ""`) case "tags": sceneRepository.tags.leftJoin(f, "tags_join", "scenes.id") f.addWhere("tags_join.scene_id IS NULL") diff --git a/pkg/sqlite/scene_test.go b/pkg/sqlite/scene_test.go index 67bf227a2..c88e42bd8 100644 --- a/pkg/sqlite/scene_test.go +++ b/pkg/sqlite/scene_test.go @@ -94,7 +94,8 @@ func Test_sceneQueryBuilder_Create(t *testing.T) { stashID1 = "stashid1" stashID2 = "stashid2" - date, _ = models.ParseDate("2003-02-01") + date, _ = models.ParseDate("2003-02-01") + productionDate, _ = models.ParseDate("2003-01-02") videoFile = makeFileWithID(fileIdxStartVideoFiles) ) @@ -107,20 +108,21 @@ func Test_sceneQueryBuilder_Create(t *testing.T) { { "full", models.Scene{ - Title: title, - Code: code, - Details: details, - Director: director, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], - CreatedAt: createdAt, - UpdatedAt: updatedAt, - GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), - TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), - PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), + Title: title, + Code: code, + Details: details, + Director: director, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + ProductionDate: &productionDate, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], + CreatedAt: createdAt, + UpdatedAt: updatedAt, + GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), + TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), + PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), Groups: models.NewRelatedGroups([]models.GroupsScenes{ { GroupID: groupIDs[groupIdxWithScene], @@ -151,15 +153,16 @@ func Test_sceneQueryBuilder_Create(t *testing.T) { { "with file", models.Scene{ - Title: title, - Code: code, - Details: details, - Director: director, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], + Title: title, + Code: code, + Details: details, + Director: director, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + ProductionDate: &productionDate, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], Files: models.NewRelatedVideoFiles([]*models.VideoFile{ videoFile.(*models.VideoFile), }), @@ -328,7 +331,8 @@ func Test_sceneQueryBuilder_Update(t *testing.T) { stashID1 = "stashid1" stashID2 = "stashid2" - date, _ = models.ParseDate("2003-02-01") + date, _ = models.ParseDate("2003-02-01") + productionDate, _ = models.ParseDate("2003-01-02") ) tests := []struct { @@ -339,21 +343,22 @@ func Test_sceneQueryBuilder_Update(t *testing.T) { { "full", &models.Scene{ - ID: sceneIDs[sceneIdxWithGallery], - Title: title, - Code: code, - Details: details, - Director: director, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], - CreatedAt: createdAt, - UpdatedAt: updatedAt, - GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), - TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), - PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), + ID: sceneIDs[sceneIdxWithGallery], + Title: title, + Code: code, + Details: details, + Director: director, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + ProductionDate: &productionDate, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], + CreatedAt: createdAt, + UpdatedAt: updatedAt, + GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), + TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), + PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), Groups: models.NewRelatedGroups([]models.GroupsScenes{ { GroupID: groupIDs[groupIdxWithScene], @@ -506,18 +511,19 @@ func Test_sceneQueryBuilder_Update(t *testing.T) { func clearScenePartial() models.ScenePartial { // leave mandatory fields return models.ScenePartial{ - Title: models.OptionalString{Set: true, Null: true}, - Code: models.OptionalString{Set: true, Null: true}, - Details: models.OptionalString{Set: true, Null: true}, - Director: 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}, - StudioID: models.OptionalInt{Set: true, Null: true}, - GalleryIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, - TagIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, - PerformerIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, - StashIDs: &models.UpdateStashIDs{Mode: models.RelationshipUpdateModeSet}, + Title: models.OptionalString{Set: true, Null: true}, + Code: models.OptionalString{Set: true, Null: true}, + Details: models.OptionalString{Set: true, Null: true}, + Director: models.OptionalString{Set: true, Null: true}, + URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet}, + Date: models.OptionalDate{Set: true, Null: true}, + ProductionDate: models.OptionalDate{Set: true, Null: true}, + Rating: models.OptionalInt{Set: true, Null: true}, + StudioID: models.OptionalInt{Set: true, Null: true}, + GalleryIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, + TagIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, + PerformerIDs: &models.UpdateIDs{Mode: models.RelationshipUpdateModeSet}, + StashIDs: &models.UpdateStashIDs{Mode: models.RelationshipUpdateModeSet}, } } @@ -540,7 +546,8 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) { stashID1 = "stashid1" stashID2 = "stashid2" - date, _ = models.ParseDate("2003-02-01") + date, _ = models.ParseDate("2003-02-01") + productionDate, _ = models.ParseDate("2003-01-02") ) tests := []struct { @@ -562,12 +569,13 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) { Values: []string{url}, Mode: models.RelationshipUpdateModeSet, }, - Date: models.NewOptionalDate(date), - Rating: models.NewOptionalInt(rating), - Organized: models.NewOptionalBool(true), - StudioID: models.NewOptionalInt(studioIDs[studioIdxWithScene]), - CreatedAt: models.NewOptionalTime(createdAt), - UpdatedAt: models.NewOptionalTime(updatedAt), + Date: models.NewOptionalDate(date), + ProductionDate: models.NewOptionalDate(productionDate), + Rating: models.NewOptionalInt(rating), + Organized: models.NewOptionalBool(true), + StudioID: models.NewOptionalInt(studioIDs[studioIdxWithScene]), + CreatedAt: models.NewOptionalTime(createdAt), + UpdatedAt: models.NewOptionalTime(updatedAt), GalleryIDs: &models.UpdateIDs{ IDs: []int{galleryIDs[galleryIdxWithScene]}, Mode: models.RelationshipUpdateModeSet, @@ -616,20 +624,21 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) { Files: models.NewRelatedVideoFiles([]*models.VideoFile{ makeSceneFile(sceneIdxWithSpacedName), }), - Title: title, - Code: code, - Details: details, - Director: director, - URLs: models.NewRelatedStrings([]string{url}), - Date: &date, - Rating: &rating, - Organized: true, - StudioID: &studioIDs[studioIdxWithScene], - CreatedAt: createdAt, - UpdatedAt: updatedAt, - GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), - TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), - PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), + Title: title, + Code: code, + Details: details, + Director: director, + URLs: models.NewRelatedStrings([]string{url}), + Date: &date, + ProductionDate: &productionDate, + Rating: &rating, + Organized: true, + StudioID: &studioIDs[studioIdxWithScene], + CreatedAt: createdAt, + UpdatedAt: updatedAt, + GalleryIDs: models.NewRelatedIDs([]int{galleryIDs[galleryIdxWithScene]}), + TagIDs: models.NewRelatedIDs([]int{tagIDs[tagIdx1WithDupName], tagIDs[tagIdx1WithScene]}), + PerformerIDs: models.NewRelatedIDs([]int{performerIDs[performerIdx1WithScene], performerIDs[performerIdx1WithDupName]}), Groups: models.NewRelatedGroups([]models.GroupsScenes{ { GroupID: groupIDs[groupIdxWithScene], @@ -3319,6 +3328,28 @@ func TestSceneQueryIsMissingDate(t *testing.T) { }) } +func TestSceneQueryIsMissingProductionDate(t *testing.T) { + withTxn(func(ctx context.Context) error { + sqb := db.Scene + isMissing := "production_date" + sceneFilter := models.SceneFilterType{ + IsMissing: &isMissing, + } + + scenes := queryScene(ctx, t, sqb, &sceneFilter, nil) + + // one in four scenes have no production date + assert.Len(t, scenes, int(math.Ceil(float64(totalScenes)/4))) + + // ensure production date is null + for _, scene := range scenes { + assert.Nil(t, scene.ProductionDate) + } + + return nil + }) +} + func TestSceneQueryIsMissingTags(t *testing.T) { withTxn(func(ctx context.Context) error { sqb := db.Scene diff --git a/pkg/sqlite/setup_test.go b/pkg/sqlite/setup_test.go index 4ab310ee7..b524d11cc 100644 --- a/pkg/sqlite/setup_test.go +++ b/pkg/sqlite/setup_test.go @@ -1190,16 +1190,17 @@ func makeScene(i int) *models.Scene { URLs: models.NewRelatedStrings([]string{ getSceneEmptyString(i, urlField), }), - Rating: getIntPtr(rating), - Date: getObjectDate(i), - StudioID: studioID, - GalleryIDs: models.NewRelatedIDs(gids), - PerformerIDs: models.NewRelatedIDs(pids), - TagIDs: models.NewRelatedIDs(tids), - Groups: models.NewRelatedGroups(groups), - StashIDs: models.NewRelatedStashIDs(sceneStashIDs(i)), - PlayDuration: getScenePlayDuration(i), - ResumeTime: getSceneResumeTime(i), + Rating: getIntPtr(rating), + Date: getObjectDate(i), + ProductionDate: getObjectDate(i), + StudioID: studioID, + GalleryIDs: models.NewRelatedIDs(gids), + PerformerIDs: models.NewRelatedIDs(pids), + TagIDs: models.NewRelatedIDs(tids), + Groups: models.NewRelatedGroups(groups), + StashIDs: models.NewRelatedStashIDs(sceneStashIDs(i)), + PlayDuration: getScenePlayDuration(i), + ResumeTime: getSceneResumeTime(i), } } diff --git a/pkg/stashbox/graphql/generated_client.go b/pkg/stashbox/graphql/generated_client.go index bc9a6ce89..bd1ef349d 100644 --- a/pkg/stashbox/graphql/generated_client.go +++ b/pkg/stashbox/graphql/generated_client.go @@ -425,19 +425,20 @@ func (t *FingerprintFragment) GetDuration() int { } type SceneFragment struct { - ID string "json:\"id\" graphql:\"id\"" - Title *string "json:\"title,omitempty\" graphql:\"title\"" - Code *string "json:\"code,omitempty\" graphql:\"code\"" - Details *string "json:\"details,omitempty\" graphql:\"details\"" - Director *string "json:\"director,omitempty\" graphql:\"director\"" - Duration *int "json:\"duration,omitempty\" graphql:\"duration\"" - Date *string "json:\"date,omitempty\" graphql:\"date\"" - Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" - Images []*ImageFragment "json:\"images\" graphql:\"images\"" - Studio *StudioFragment "json:\"studio,omitempty\" graphql:\"studio\"" - Tags []*TagFragment "json:\"tags\" graphql:\"tags\"" - Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\"" - Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\"" + ID string "json:\"id\" graphql:\"id\"" + Title *string "json:\"title,omitempty\" graphql:\"title\"" + Code *string "json:\"code,omitempty\" graphql:\"code\"" + Details *string "json:\"details,omitempty\" graphql:\"details\"" + Director *string "json:\"director,omitempty\" graphql:\"director\"" + Duration *int "json:\"duration,omitempty\" graphql:\"duration\"" + Date *string "json:\"date,omitempty\" graphql:\"date\"" + ProductionDate *string "json:\"production_date,omitempty\" graphql:\"production_date\"" + Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" + Images []*ImageFragment "json:\"images\" graphql:\"images\"" + Studio *StudioFragment "json:\"studio,omitempty\" graphql:\"studio\"" + Tags []*TagFragment "json:\"tags\" graphql:\"tags\"" + Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\"" + Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\"" } func (t *SceneFragment) GetID() string { @@ -482,6 +483,12 @@ func (t *SceneFragment) GetDate() *string { } return t.Date } +func (t *SceneFragment) GetProductionDate() *string { + if t == nil { + t = &SceneFragment{} + } + return t.ProductionDate +} func (t *SceneFragment) GetUrls() []*URLFragment { if t == nil { t = &SceneFragment{} @@ -998,6 +1005,7 @@ fragment SceneFragment on Scene { director duration date + production_date urls { ... URLFragment } @@ -1141,6 +1149,7 @@ fragment SceneFragment on Scene { director duration date + production_date urls { ... URLFragment } @@ -1442,6 +1451,7 @@ fragment SceneFragment on Scene { director duration date + production_date urls { ... URLFragment } diff --git a/pkg/stashbox/scene.go b/pkg/stashbox/scene.go index 9ba4d1a22..eeeb016f7 100644 --- a/pkg/stashbox/scene.go +++ b/pkg/stashbox/scene.go @@ -158,15 +158,16 @@ func (c Client) sceneFragmentToScrapedScene(ctx context.Context, s *graphql.Scen stashID := s.ID ss := &models.ScrapedScene{ - Title: s.Title, - Code: s.Code, - Date: s.Date, - Details: s.Details, - Director: s.Director, - URL: findURL(s.Urls, "STUDIO"), - Duration: s.Duration, - RemoteSiteID: &stashID, - Fingerprints: getFingerprints(s), + Title: s.Title, + Code: s.Code, + Date: s.Date, + ProductionDate: s.ProductionDate, + Details: s.Details, + Director: s.Director, + URL: findURL(s.Urls, "STUDIO"), + Duration: s.Duration, + RemoteSiteID: &stashID, + Fingerprints: getFingerprints(s), // Image // stash_id } @@ -292,6 +293,11 @@ func newSceneDraftInput(d SceneDraft, endpoint string) graphql.SceneDraftInput { draft.Date = &v } + if scene.ProductionDate != nil { + v := scene.ProductionDate.String() + draft.ProductionDate = &v + } + if d.Studio != nil { studio := d.Studio diff --git a/ui/v2.5/graphql/data/scene-slim.graphql b/ui/v2.5/graphql/data/scene-slim.graphql index d5899a247..5e339bab7 100644 --- a/ui/v2.5/graphql/data/scene-slim.graphql +++ b/ui/v2.5/graphql/data/scene-slim.graphql @@ -6,6 +6,7 @@ fragment SlimSceneData on Scene { director urls date + production_date rating100 o_counter organized diff --git a/ui/v2.5/graphql/data/scene.graphql b/ui/v2.5/graphql/data/scene.graphql index b7378c1da..b735a987e 100644 --- a/ui/v2.5/graphql/data/scene.graphql +++ b/ui/v2.5/graphql/data/scene.graphql @@ -6,6 +6,7 @@ fragment SceneData on Scene { director urls date + production_date rating100 o_counter organized @@ -87,6 +88,7 @@ fragment SelectSceneData on Scene { id title date + production_date code studio { name diff --git a/ui/v2.5/graphql/data/scrapers.graphql b/ui/v2.5/graphql/data/scrapers.graphql index 0dae3c2d5..d7845e003 100644 --- a/ui/v2.5/graphql/data/scrapers.graphql +++ b/ui/v2.5/graphql/data/scrapers.graphql @@ -177,6 +177,7 @@ fragment ScrapedSceneData on ScrapedScene { director urls date + production_date image remote_site_id @@ -263,6 +264,7 @@ fragment ScrapedStashBoxSceneData on ScrapedScene { director url date + production_date image remote_site_id duration diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx index b109016b1..50f04d83c 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneDetailPanel.tsx @@ -52,7 +52,9 @@ export const SceneDetailPanel: React.FC = (props) => { )); @@ -86,6 +88,12 @@ export const SceneDetailPanel: React.FC = (props) => { :{" "} {TextUtils.formatDateTime(intl, props.scene.updated_at)}{" "} + {props.scene.production_date && ( +
+ :{" "} + {TextUtils.formatDate(intl, props.scene.production_date)} +
+ )} {props.scene.code && (
: {props.scene.code}{" "} diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx index 2e8bb39fa..6630a710d 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx @@ -129,6 +129,7 @@ export const SceneEditPanel: React.FC = ({ code: yup.string().ensure(), urls: yupUniqueStringList(intl), date: yupDateString(intl), + production_date: yupDateString(intl), director: yup.string().ensure(), gallery_ids: yup.array(yup.string().required()).defined(), studio_id: yup.string().required().nullable(), @@ -154,6 +155,7 @@ export const SceneEditPanel: React.FC = ({ code: scene.code ?? "", urls: scene.urls ?? [], date: scene.date ?? "", + production_date: scene.production_date ?? "", director: scene.director ?? "", gallery_ids: (scene.galleries ?? []).map((g) => g.id), studio_id: scene.studio?.id ?? null, @@ -352,6 +354,7 @@ export const SceneEditPanel: React.FC = ({ try { const input: GQL.ScrapedSceneInput = { date: fragment.date, + production_date: fragment.production_date, code: fragment.code, details: fragment.details, director: fragment.director, @@ -484,6 +487,10 @@ export const SceneEditPanel: React.FC = ({ formik.setFieldValue("date", updatedScene.date); } + if (updatedScene.production_date) { + formik.setFieldValue("production_date", updatedScene.production_date); + } + if (updatedScene.urls) { formik.setFieldValue("urls", updatedScene.urls); } @@ -849,6 +856,7 @@ export const SceneEditPanel: React.FC = ({ )} {renderDateField("date")} + {renderDateField("production_date")} {renderInputField("director")} {renderGalleriesField()} diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx index 9b9a6bc40..045b82080 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx @@ -72,6 +72,9 @@ export const SceneScrapeDialog: React.FC = ({ const [date, setDate] = useState>( new ScrapeResult(scene.date, scraped.date) ); + const [production_date, setProductionDate] = useState>( + new ScrapeResult(scene.production_date, scraped.production_date) + ); const [director, setDirector] = useState>( new ScrapeResult(scene.director, scraped.director) ); @@ -177,6 +180,7 @@ export const SceneScrapeDialog: React.FC = ({ code, urls, date, + production_date, director, studio, performers, @@ -203,6 +207,7 @@ export const SceneScrapeDialog: React.FC = ({ code: code.getNewValue(), urls: urls.getNewValue(), date: date.getNewValue(), + production_date: production_date.getNewValue(), director: director.getNewValue(), studio: newStudioValue, performers: performers.getNewValue(), @@ -242,6 +247,13 @@ export const SceneScrapeDialog: React.FC = ({ result={date} onChange={(value) => setDate(value)} /> + setProductionDate(value)} + /> = ({ title: resolveField("title", stashScene.title, scene.title), details: resolveField("details", stashScene.details, scene.details), date: resolveField("date", stashScene.date, scene.date), + production_date: resolveField( + "production_date", + stashScene.production_date, + scene.production_date + ), performer_ids: uniq( stashScene.performers.map((p) => p.id).concat(filteredPerformerIDs) ), @@ -509,6 +514,7 @@ const StashSearchResult: React.FC = ({ cover_image: "cover_image", title: "title", date: "date", + production_date: "production_date", url: "url", details: "details", studio: "studio", @@ -625,6 +631,21 @@ const StashSearchResult: React.FC = ({ } }; + const maybeRenderProductionDateField = () => { + if (isActive && scene.production_date) { + return ( +
+ setExcludedField(fields.production_date, v)} + > + {scene.production_date} + +
+ ); + } + }; + const maybeRenderDirector = () => { if (scene.director) { return ( @@ -832,6 +853,7 @@ const StashSearchResult: React.FC = ({ {maybeRenderStudioCode()} {maybeRenderDateField()} + {maybeRenderProductionDateField()} {getDurationStatus(scene, stashSceneFile?.duration)} {getFingerprintStatus(scene, stashScene)} diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 4974c06ca..748f8c0e7 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -977,6 +977,7 @@ "value": "Value" }, "date": "Date", + "production_date": "Production Date", "date_format": "YYYY-MM-DD", "datetime_format": "YYYY-MM-DD HH:MM", "death_date": "Death Date", diff --git a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts index 821870e47..b6616503b 100644 --- a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts +++ b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts @@ -31,6 +31,7 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOption( "director", "url", "date", + "production_date", "rating", "cover", "galleries", diff --git a/ui/v2.5/src/models/list-filter/scenes.ts b/ui/v2.5/src/models/list-filter/scenes.ts index ef7c62802..ed0df9e4e 100644 --- a/ui/v2.5/src/models/list-filter/scenes.ts +++ b/ui/v2.5/src/models/list-filter/scenes.ts @@ -42,6 +42,7 @@ const defaultSortBy = "date"; const sortByOptions = [ "organized", "date", + "production_date", "file_count", "filesize", "duration", @@ -142,6 +143,7 @@ const criterionOptions = [ createMandatoryNumberCriterionOption("interactive_speed"), createMandatoryNumberCriterionOption("file_count"), createDateCriterionOption("date"), + createDateCriterionOption("production_date"), createMandatoryTimestampCriterionOption("created_at"), createMandatoryTimestampCriterionOption("updated_at"), CustomFieldsCriterionOption, diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index d5ae684fc..77a697203 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -210,6 +210,7 @@ export type CriterionType = | "stash_id_endpoint" | "stash_id_count" | "date" + | "production_date" | "created_at" | "updated_at" | "birthdate"