This commit is contained in:
Emilo2 2026-02-06 07:45:30 +00:00 committed by GitHub
commit 993e81c031
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 404 additions and 229 deletions

View file

@ -328,6 +328,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"

View file

@ -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!
@ -101,6 +102,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
@ -132,6 +134,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
@ -181,6 +184,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
@ -237,6 +241,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

View file

@ -83,6 +83,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
@ -107,6 +108,7 @@ input ScrapedSceneInput {
url: String @deprecated(reason: "use urls")
urls: [String!]
date: String
production_date: String
# no image, file, duration or relationships

View file

@ -100,6 +100,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
...URLFragment
}

View file

@ -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 {

View file

@ -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 date: %w", err)
}
newScene.StudioID, err = translator.intPtrFromString(input.StudioID)
if err != nil {
return nil, fmt.Errorf("converting studio id: %w", err)
@ -200,6 +204,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)
@ -355,6 +363,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)

View file

@ -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 {

View file

@ -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"`

View file

@ -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"`
@ -56,11 +57,12 @@ func NewScene() Scene {
// 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
@ -192,27 +194,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

View file

@ -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,
},
},
{

View file

@ -576,13 +576,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"`
@ -599,14 +600,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 {

View file

@ -101,6 +101,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
@ -153,21 +155,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"`
@ -187,6 +190,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"`

View file

@ -44,6 +44,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
}

View file

@ -106,6 +106,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
}

View file

@ -43,6 +43,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)

View file

@ -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) {

View file

@ -34,7 +34,7 @@ const (
cacheSizeEnv = "STASH_SQLITE_CACHE_SIZE"
)
var appSchemaVersion uint = 77
var appSchemaVersion uint = 78
//go:embed migrations/*.sql
var migrationsBox embed.FS

View file

@ -0,0 +1,2 @@
ALTER TABLE `scenes` ADD COLUMN `production_date` date;
ALTER TABLE `scenes` ADD COLUMN `production_date_precision` TINYINT;

View file

@ -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)
@ -1117,6 +1123,7 @@ var sceneSortOptions = sortOptions{
"created_at",
"code",
"date",
"production_date",
"file_count",
"filesize",
"duration",

View file

@ -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],
@ -3292,6 +3301,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

View file

@ -1174,16 +1174,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),
}
}

View file

@ -406,19 +406,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 {
@ -463,6 +464,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{}
@ -862,6 +869,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
... URLFragment
}
@ -998,6 +1006,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
... URLFragment
}
@ -1134,6 +1143,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
... URLFragment
}
@ -1270,6 +1280,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
... URLFragment
}
@ -1564,6 +1575,7 @@ fragment SceneFragment on Scene {
director
duration
date
production_date
urls {
... URLFragment
}

View file

@ -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
}
@ -296,6 +297,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

View file

@ -6,6 +6,7 @@ fragment SlimSceneData on Scene {
director
urls
date
production_date
rating100
o_counter
organized

View file

@ -6,6 +6,7 @@ fragment SceneData on Scene {
director
urls
date
production_date
rating100
o_counter
organized
@ -85,6 +86,7 @@ fragment SelectSceneData on Scene {
id
title
date
production_date
code
studio {
name

View file

@ -168,6 +168,7 @@ fragment ScrapedSceneData on ScrapedScene {
director
urls
date
production_date
image
remote_site_id
@ -254,6 +255,7 @@ fragment ScrapedStashBoxSceneData on ScrapedScene {
director
url
date
production_date
image
remote_site_id
duration

View file

@ -124,6 +124,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
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(),
@ -148,6 +149,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
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,
@ -332,6 +334,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
try {
const input: GQL.ScrapedSceneInput = {
date: fragment.date,
production_date: fragment.production_date,
code: fragment.code,
details: fragment.details,
director: fragment.director,
@ -464,6 +467,10 @@ export const SceneEditPanel: React.FC<IProps> = ({
formik.setFieldValue("date", updatedScene.date);
}
if (updatedScene.production_date) {
formik.setFieldValue("production_date", updatedScene.production_date);
}
if (updatedScene.urls) {
formik.setFieldValue("urls", updatedScene.urls);
}
@ -825,6 +832,7 @@ export const SceneEditPanel: React.FC<IProps> = ({
)}
{renderDateField("date")}
{renderDateField("production_date")}
{renderInputField("director")}
{renderGalleriesField()}

View file

@ -72,6 +72,9 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
const [date, setDate] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.date, scraped.date)
);
const [production_date, setProductionDate] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.production_date, scraped.production_date)
);
const [director, setDirector] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(scene.director, scraped.director)
);
@ -177,6 +180,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
code,
urls,
date,
production_date,
director,
studio,
performers,
@ -203,6 +207,7 @@ export const SceneScrapeDialog: React.FC<ISceneScrapeDialogProps> = ({
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<ISceneScrapeDialogProps> = ({
result={date}
onChange={(value) => setDate(value)}
/>
<ScrapedInputGroupRow
field="production_date"
title={intl.formatMessage({ id: "production_date" })}
placeholder="YYYY-MM-DD"
result={production_date}
onChange={(value) => setProductionDate(value)}
/>
<ScrapedInputGroupRow
field="director"
title={intl.formatMessage({ id: "director" })}

View file

@ -381,6 +381,11 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
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)
),
@ -514,6 +519,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
cover_image: "cover_image",
title: "title",
date: "date",
production_date: "production_date",
url: "url",
details: "details",
studio: "studio",
@ -630,6 +636,21 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
}
};
const maybeRenderProductionDateField = () => {
if (isActive && scene.production_date) {
return (
<h5>
<OptionalField
exclude={excludedFields[fields.production_date]}
setExclude={(v) => setExcludedField(fields.production_date, v)}
>
{scene.production_date}
</OptionalField>
</h5>
);
}
};
const maybeRenderDirector = () => {
if (scene.director) {
return (
@ -833,6 +854,7 @@ const StashSearchResult: React.FC<IStashSearchResultProps> = ({
{maybeRenderStudioCode()}
{maybeRenderDateField()}
{maybeRenderProductionDateField()}
{getDurationStatus(scene, stashSceneFile?.duration)}
{getFingerprintStatus(scene, stashScene)}
</div>

View file

@ -924,6 +924,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",

View file

@ -30,6 +30,7 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOption(
"details",
"url",
"date",
"production_date",
"galleries",
"studio",
"group",

View file

@ -40,6 +40,7 @@ const defaultSortBy = "date";
const sortByOptions = [
"organized",
"date",
"production_date",
"file_count",
"filesize",
"duration",
@ -139,6 +140,7 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("interactive_speed"),
createMandatoryNumberCriterionOption("file_count"),
createDateCriterionOption("date"),
createDateCriterionOption("production_date"),
createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"),
];

View file

@ -202,6 +202,7 @@ export type CriterionType =
| "stash_id_endpoint"
| "stash_id_count"
| "date"
| "production_date"
| "created_at"
| "updated_at"
| "birthdate"