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>
This commit is contained in:
bob123491234
2023-11-27 22:05:33 -06:00
committed by GitHub
parent b78771dbcd
commit d1018b4c5d
25 changed files with 243 additions and 102 deletions

View File

@@ -1,9 +1,11 @@
fragment SlimGalleryData on Gallery { fragment SlimGalleryData on Gallery {
id id
title title
code
date date
urls urls
details details
photographer
rating100 rating100
organized organized
files { files {

View File

@@ -3,9 +3,11 @@ fragment GalleryData on Gallery {
created_at created_at
updated_at updated_at
title title
code
date date
urls urls
details details
photographer
rating100 rating100
organized organized

View File

@@ -184,8 +184,10 @@ fragment ScrapedSceneData on ScrapedScene {
fragment ScrapedGalleryData on ScrapedGallery { fragment ScrapedGalleryData on ScrapedGallery {
title title
code
details details
urls urls
photographer
date date
studio { studio {

View File

@@ -377,6 +377,10 @@ input GalleryFilterType {
created_at: TimestampCriterionInput created_at: TimestampCriterionInput
"Filter by last update time" "Filter by last update time"
updated_at: TimestampCriterionInput updated_at: TimestampCriterionInput
"Filter by studio code"
code: StringCriterionInput
"Filter by photographer"
photographer: StringCriterionInput
} }
input TagFilterType { input TagFilterType {

View File

@@ -2,10 +2,12 @@
type Gallery { type Gallery {
id: ID! id: ID!
title: String title: String
code: String
url: String @deprecated(reason: "Use urls") url: String @deprecated(reason: "Use urls")
urls: [String!]! urls: [String!]!
date: String date: String
details: String details: String
photographer: String
# rating expressed as 1-100 # rating expressed as 1-100
rating100: Int rating100: Int
organized: Boolean! organized: Boolean!
@@ -27,10 +29,12 @@ type Gallery {
input GalleryCreateInput { input GalleryCreateInput {
title: String! title: String!
code: String
url: String @deprecated(reason: "Use urls") url: String @deprecated(reason: "Use urls")
urls: [String!] urls: [String!]
date: String date: String
details: String details: String
photographer: String
# rating expressed as 1-100 # rating expressed as 1-100
rating100: Int rating100: Int
organized: Boolean organized: Boolean
@@ -44,10 +48,12 @@ input GalleryUpdateInput {
clientMutationId: String clientMutationId: String
id: ID! id: ID!
title: String title: String
code: String
url: String @deprecated(reason: "Use urls") url: String @deprecated(reason: "Use urls")
urls: [String!] urls: [String!]
date: String date: String
details: String details: String
photographer: String
# rating expressed as 1-100 # rating expressed as 1-100
rating100: Int rating100: Int
organized: Boolean organized: Boolean
@@ -62,10 +68,12 @@ input GalleryUpdateInput {
input BulkGalleryUpdateInput { input BulkGalleryUpdateInput {
clientMutationId: String clientMutationId: String
ids: [ID!] ids: [ID!]
code: String
url: String @deprecated(reason: "Use urls") url: String @deprecated(reason: "Use urls")
urls: BulkUpdateStrings urls: BulkUpdateStrings
date: String date: String
details: String details: String
photographer: String
# rating expressed as 1-100 # rating expressed as 1-100
rating100: Int rating100: Int
organized: Boolean organized: Boolean

View File

@@ -99,7 +99,9 @@ input ScrapedSceneInput {
type ScrapedGallery { type ScrapedGallery {
title: String title: String
code: String
details: String details: String
photographer: String
url: String @deprecated(reason: "use urls") url: String @deprecated(reason: "use urls")
urls: [String!] urls: [String!]
date: String date: String
@@ -111,7 +113,9 @@ type ScrapedGallery {
input ScrapedGalleryInput { input ScrapedGalleryInput {
title: String title: String
code: String
details: String details: String
photographer: String
url: String @deprecated(reason: "use urls") url: String @deprecated(reason: "use urls")
urls: [String!] urls: [String!]
date: String date: String

View File

@@ -43,7 +43,9 @@ func (r *mutationResolver) GalleryCreate(ctx context.Context, input GalleryCreat
newGallery := models.NewGallery() newGallery := models.NewGallery()
newGallery.Title = input.Title newGallery.Title = input.Title
newGallery.Code = translator.string(input.Code)
newGallery.Details = translator.string(input.Details) newGallery.Details = translator.string(input.Details)
newGallery.Photographer = translator.string(input.Photographer)
newGallery.Rating = input.Rating100 newGallery.Rating = input.Rating100
var err error var err error
@@ -182,7 +184,9 @@ func (r *mutationResolver) galleryUpdate(ctx context.Context, input models.Galle
updatedGallery.Title = models.NewOptionalString(*input.Title) updatedGallery.Title = models.NewOptionalString(*input.Title)
} }
updatedGallery.Code = translator.optionalString(input.Code, "code")
updatedGallery.Details = translator.optionalString(input.Details, "details") updatedGallery.Details = translator.optionalString(input.Details, "details")
updatedGallery.Photographer = translator.optionalString(input.Photographer, "photographer")
updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100") updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100")
updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") 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 // Populate gallery from the input
updatedGallery := models.NewGalleryPartial() updatedGallery := models.NewGalleryPartial()
updatedGallery.Code = translator.optionalString(input.Code, "code")
updatedGallery.Details = translator.optionalString(input.Details, "details") updatedGallery.Details = translator.optionalString(input.Details, "details")
updatedGallery.Photographer = translator.optionalString(input.Photographer, "photographer")
updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100") updatedGallery.Rating = translator.optionalInt(input.Rating100, "rating100")
updatedGallery.Organized = translator.optionalBool(input.Organized, "organized") updatedGallery.Organized = translator.optionalBool(input.Organized, "organized")
updatedGallery.URLs = translator.optionalURLsBulk(input.Urls, input.URL) updatedGallery.URLs = translator.optionalURLsBulk(input.Urls, input.URL)

View File

@@ -14,8 +14,10 @@ import (
func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) { func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
newGalleryJSON := jsonschema.Gallery{ newGalleryJSON := jsonschema.Gallery{
Title: gallery.Title, Title: gallery.Title,
Code: gallery.Code,
URLs: gallery.URLs.List(), URLs: gallery.URLs.List(),
Details: gallery.Details, Details: gallery.Details,
Photographer: gallery.Photographer,
CreatedAt: json.JSONTime{Time: gallery.CreatedAt}, CreatedAt: json.JSONTime{Time: gallery.CreatedAt},
UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt}, UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt},
} }

View File

@@ -62,9 +62,15 @@ func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.G
if galleryJSON.Title != "" { if galleryJSON.Title != "" {
newGallery.Title = galleryJSON.Title newGallery.Title = galleryJSON.Title
} }
if galleryJSON.Code != "" {
newGallery.Code = galleryJSON.Code
}
if galleryJSON.Details != "" { if galleryJSON.Details != "" {
newGallery.Details = galleryJSON.Details newGallery.Details = galleryJSON.Details
} }
if galleryJSON.Photographer != "" {
newGallery.Photographer = galleryJSON.Photographer
}
if len(galleryJSON.URLs) > 0 { if len(galleryJSON.URLs) > 0 {
newGallery.URLs = models.NewRelatedStrings(galleryJSON.URLs) newGallery.URLs = models.NewRelatedStrings(galleryJSON.URLs)
} else if galleryJSON.URL != "" { } else if galleryJSON.URL != "" {

View File

@@ -6,7 +6,9 @@ type GalleryFilterType struct {
Not *GalleryFilterType `json:"NOT"` Not *GalleryFilterType `json:"NOT"`
ID *IntCriterionInput `json:"id"` ID *IntCriterionInput `json:"id"`
Title *StringCriterionInput `json:"title"` Title *StringCriterionInput `json:"title"`
Code *StringCriterionInput `json:"code"`
Details *StringCriterionInput `json:"details"` Details *StringCriterionInput `json:"details"`
Photographer *StringCriterionInput `json:"photographer"`
// Filter by file checksum // Filter by file checksum
Checksum *StringCriterionInput `json:"checksum"` Checksum *StringCriterionInput `json:"checksum"`
// Filter by path // Filter by path
@@ -57,9 +59,11 @@ type GalleryUpdateInput struct {
ClientMutationID *string `json:"clientMutationId"` ClientMutationID *string `json:"clientMutationId"`
ID string `json:"id"` ID string `json:"id"`
Title *string `json:"title"` Title *string `json:"title"`
Code *string `json:"code"`
Urls []string `json:"urls"` Urls []string `json:"urls"`
Date *string `json:"date"` Date *string `json:"date"`
Details *string `json:"details"` Details *string `json:"details"`
Photographer *string `json:"photographer"`
Rating100 *int `json:"rating100"` Rating100 *int `json:"rating100"`
Organized *bool `json:"organized"` Organized *bool `json:"organized"`
SceneIds []string `json:"scene_ids"` SceneIds []string `json:"scene_ids"`

View File

@@ -21,9 +21,11 @@ type Gallery struct {
ZipFiles []string `json:"zip_files,omitempty"` ZipFiles []string `json:"zip_files,omitempty"`
FolderPath string `json:"folder_path,omitempty"` FolderPath string `json:"folder_path,omitempty"`
Title string `json:"title,omitempty"` Title string `json:"title,omitempty"`
Code string `json:"code,omitempty"`
URLs []string `json:"urls,omitempty"` URLs []string `json:"urls,omitempty"`
Date string `json:"date,omitempty"` Date string `json:"date,omitempty"`
Details string `json:"details,omitempty"` Details string `json:"details,omitempty"`
Photographer string `json:"photographer,omitempty"`
Rating int `json:"rating,omitempty"` Rating int `json:"rating,omitempty"`
Organized bool `json:"organized,omitempty"` Organized bool `json:"organized,omitempty"`
Chapters []GalleryChapter `json:"chapters,omitempty"` Chapters []GalleryChapter `json:"chapters,omitempty"`

View File

@@ -11,8 +11,10 @@ type Gallery struct {
ID int `json:"id"` ID int `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Code string `json:"code"`
Date *Date `json:"date"` Date *Date `json:"date"`
Details string `json:"details"` Details string `json:"details"`
Photographer string `json:"photographer"`
// Rating expressed in 1-100 scale // Rating expressed in 1-100 scale
Rating *int `json:"rating"` Rating *int `json:"rating"`
Organized bool `json:"organized"` Organized bool `json:"organized"`
@@ -51,9 +53,11 @@ type GalleryPartial struct {
// Checksum OptionalString // Checksum OptionalString
// Zip OptionalBool // Zip OptionalBool
Title OptionalString Title OptionalString
Code OptionalString
URLs *UpdateStrings URLs *UpdateStrings
Date OptionalDate Date OptionalDate
Details OptionalString Details OptionalString
Photographer OptionalString
// Rating expressed in 1-100 scale // Rating expressed in 1-100 scale
Rating OptionalInt Rating OptionalInt
Organized OptionalBool Organized OptionalBool

View File

@@ -4,7 +4,9 @@ import "github.com/stashapp/stash/pkg/models"
type ScrapedGallery struct { type ScrapedGallery struct {
Title *string `json:"title"` Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"` Details *string `json:"details"`
Photographer *string `json:"photographer"`
URLs []string `json:"urls"` URLs []string `json:"urls"`
Date *string `json:"date"` Date *string `json:"date"`
Studio *models.ScrapedStudio `json:"studio"` Studio *models.ScrapedStudio `json:"studio"`
@@ -19,7 +21,9 @@ func (ScrapedGallery) IsScrapedContent() {}
type ScrapedGalleryInput struct { type ScrapedGalleryInput struct {
Title *string `json:"title"` Title *string `json:"title"`
Code *string `json:"code"`
Details *string `json:"details"` Details *string `json:"details"`
Photographer *string `json:"photographer"`
URLs []string `json:"urls"` URLs []string `json:"urls"`
Date *string `json:"date"` Date *string `json:"date"`

View File

@@ -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) { func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*ScrapedScene, error) {
sceneScraperConfig := s.Scene sceneScraperConfig := s.Scene
sceneMap := sceneScraperConfig.mappedConfig if sceneScraperConfig == nil {
if sceneMap == nil {
return nil, nil return nil, nil
} }
sceneMap := sceneScraperConfig.mappedConfig
logger.Debug(`Processing scene:`) logger.Debug(`Processing scene:`)
results := sceneMap.process(ctx, q, s.Common) results := sceneMap.process(ctx, q, s.Common)
@@ -1000,11 +1001,12 @@ func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*Scrap
var ret ScrapedGallery var ret ScrapedGallery
galleryScraperConfig := s.Gallery galleryScraperConfig := s.Gallery
galleryMap := galleryScraperConfig.mappedConfig if galleryScraperConfig == nil {
if galleryMap == nil {
return nil, nil return nil, nil
} }
galleryMap := galleryScraperConfig.mappedConfig
galleryPerformersMap := galleryScraperConfig.Performers galleryPerformersMap := galleryScraperConfig.Performers
galleryTagsMap := galleryScraperConfig.Tags galleryTagsMap := galleryScraperConfig.Tags
galleryStudioMap := galleryScraperConfig.Studio galleryStudioMap := galleryScraperConfig.Studio
@@ -1062,11 +1064,12 @@ func (s mappedScraper) scrapeMovie(ctx context.Context, q mappedQuery) (*models.
var ret models.ScrapedMovie var ret models.ScrapedMovie
movieScraperConfig := s.Movie movieScraperConfig := s.Movie
movieMap := movieScraperConfig.mappedConfig if movieScraperConfig == nil {
if movieMap == nil {
return nil, nil return nil, nil
} }
movieMap := movieScraperConfig.mappedConfig
movieStudioMap := movieScraperConfig.Studio movieStudioMap := movieScraperConfig.Studio
results := movieMap.process(ctx, q, s.Common) results := movieMap.process(ctx, q, s.Common)

View File

@@ -33,7 +33,7 @@ const (
dbConnTimeout = 30 dbConnTimeout = 30
) )
var appSchemaVersion uint = 52 var appSchemaVersion uint = 53
//go:embed migrations/*.sql //go:embed migrations/*.sql
var migrationsBox embed.FS var migrationsBox embed.FS

View File

@@ -33,8 +33,10 @@ const (
type galleryRow struct { type galleryRow struct {
ID int `db:"id" goqu:"skipinsert"` ID int `db:"id" goqu:"skipinsert"`
Title zero.String `db:"title"` Title zero.String `db:"title"`
Code zero.String `db:"code"`
Date NullDate `db:"date"` Date NullDate `db:"date"`
Details zero.String `db:"details"` Details zero.String `db:"details"`
Photographer zero.String `db:"photographer"`
// expressed as 1-100 // expressed as 1-100
Rating null.Int `db:"rating"` Rating null.Int `db:"rating"`
Organized bool `db:"organized"` Organized bool `db:"organized"`
@@ -47,8 +49,10 @@ type galleryRow struct {
func (r *galleryRow) fromGallery(o models.Gallery) { func (r *galleryRow) fromGallery(o models.Gallery) {
r.ID = o.ID r.ID = o.ID
r.Title = zero.StringFrom(o.Title) r.Title = zero.StringFrom(o.Title)
r.Code = zero.StringFrom(o.Code)
r.Date = NullDateFromDatePtr(o.Date) r.Date = NullDateFromDatePtr(o.Date)
r.Details = zero.StringFrom(o.Details) r.Details = zero.StringFrom(o.Details)
r.Photographer = zero.StringFrom(o.Photographer)
r.Rating = intFromPtr(o.Rating) r.Rating = intFromPtr(o.Rating)
r.Organized = o.Organized r.Organized = o.Organized
r.StudioID = intFromPtr(o.StudioID) r.StudioID = intFromPtr(o.StudioID)
@@ -70,8 +74,10 @@ func (r *galleryQueryRow) resolve() *models.Gallery {
ret := &models.Gallery{ ret := &models.Gallery{
ID: r.ID, ID: r.ID,
Title: r.Title.String, Title: r.Title.String,
Code: r.Code.String,
Date: r.Date.DatePtr(), Date: r.Date.DatePtr(),
Details: r.Details.String, Details: r.Details.String,
Photographer: r.Photographer.String,
Rating: nullIntPtr(r.Rating), Rating: nullIntPtr(r.Rating),
Organized: r.Organized, Organized: r.Organized,
StudioID: nullIntPtr(r.StudioID), StudioID: nullIntPtr(r.StudioID),
@@ -96,8 +102,10 @@ type galleryRowRecord struct {
func (r *galleryRowRecord) fromPartial(o models.GalleryPartial) { func (r *galleryRowRecord) fromPartial(o models.GalleryPartial) {
r.setNullString("title", o.Title) r.setNullString("title", o.Title)
r.setNullString("code", o.Code)
r.setNullDate("date", o.Date) r.setNullDate("date", o.Date)
r.setNullString("details", o.Details) r.setNullString("details", o.Details)
r.setNullString("photographer", o.Photographer)
r.setNullInt("rating", o.Rating) r.setNullInt("rating", o.Rating)
r.setBool("organized", o.Organized) r.setBool("organized", o.Organized)
r.setNullInt("studio_id", o.StudioID) 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, intCriterionHandler(galleryFilter.ID, "galleries.id", nil))
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Title, "galleries.title")) 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.Details, "galleries.details"))
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Photographer, "galleries.photographer"))
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) { query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
if galleryFilter.Checksum != nil { if galleryFilter.Checksum != nil {

View File

@@ -57,9 +57,11 @@ func loadGalleryRelationships(ctx context.Context, expected models.Gallery, actu
func Test_galleryQueryBuilder_Create(t *testing.T) { func Test_galleryQueryBuilder_Create(t *testing.T) {
var ( var (
title = "title" title = "title"
code = "1337"
url = "url" url = "url"
rating = 60 rating = 60
details = "details" details = "details"
photographer = "photographer"
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
@@ -77,9 +79,11 @@ func Test_galleryQueryBuilder_Create(t *testing.T) {
"full", "full",
models.Gallery{ models.Gallery{
Title: title, Title: title,
Code: code,
URLs: models.NewRelatedStrings([]string{url}), URLs: models.NewRelatedStrings([]string{url}),
Date: &date, Date: &date,
Details: details, Details: details,
Photographer: photographer,
Rating: &rating, Rating: &rating,
Organized: true, Organized: true,
StudioID: &studioIDs[studioIdxWithScene], StudioID: &studioIDs[studioIdxWithScene],
@@ -95,9 +99,11 @@ func Test_galleryQueryBuilder_Create(t *testing.T) {
"with file", "with file",
models.Gallery{ models.Gallery{
Title: title, Title: title,
Code: code,
URLs: models.NewRelatedStrings([]string{url}), URLs: models.NewRelatedStrings([]string{url}),
Date: &date, Date: &date,
Details: details, Details: details,
Photographer: photographer,
Rating: &rating, Rating: &rating,
Organized: true, Organized: true,
StudioID: &studioIDs[studioIdxWithScene], StudioID: &studioIDs[studioIdxWithScene],
@@ -208,9 +214,11 @@ func makeGalleryFileWithID(i int) *models.BaseFile {
func Test_galleryQueryBuilder_Update(t *testing.T) { func Test_galleryQueryBuilder_Update(t *testing.T) {
var ( var (
title = "title" title = "title"
code = "code"
url = "url" url = "url"
rating = 60 rating = 60
details = "details" details = "details"
photographer = "photographer"
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
) )
@@ -227,9 +235,11 @@ func Test_galleryQueryBuilder_Update(t *testing.T) {
&models.Gallery{ &models.Gallery{
ID: galleryIDs[galleryIdxWithScene], ID: galleryIDs[galleryIdxWithScene],
Title: title, Title: title,
Code: code,
URLs: models.NewRelatedStrings([]string{url}), URLs: models.NewRelatedStrings([]string{url}),
Date: &date, Date: &date,
Details: details, Details: details,
Photographer: photographer,
Rating: &rating, Rating: &rating,
Organized: true, Organized: true,
StudioID: &studioIDs[studioIdxWithScene], StudioID: &studioIDs[studioIdxWithScene],
@@ -389,7 +399,9 @@ func clearGalleryPartial() models.GalleryPartial {
// leave mandatory fields // leave mandatory fields
return models.GalleryPartial{ return models.GalleryPartial{
Title: models.OptionalString{Set: true, Null: true}, Title: models.OptionalString{Set: true, Null: true},
Code: models.OptionalString{Set: true, Null: true},
Details: 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}, URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet},
Date: models.OptionalDate{Set: true, Null: true}, Date: models.OptionalDate{Set: true, Null: true},
Rating: models.OptionalInt{Set: true, Null: true}, Rating: models.OptionalInt{Set: true, Null: true},
@@ -402,7 +414,9 @@ func clearGalleryPartial() models.GalleryPartial {
func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) { func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
var ( var (
title = "title" title = "title"
code = "code"
details = "details" details = "details"
photographer = "photographer"
url = "url" url = "url"
rating = 60 rating = 60
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
@@ -423,7 +437,9 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
galleryIDs[galleryIdxWithImage], galleryIDs[galleryIdxWithImage],
models.GalleryPartial{ models.GalleryPartial{
Title: models.NewOptionalString(title), Title: models.NewOptionalString(title),
Code: models.NewOptionalString(code),
Details: models.NewOptionalString(details), Details: models.NewOptionalString(details),
Photographer: models.NewOptionalString(photographer),
URLs: &models.UpdateStrings{ URLs: &models.UpdateStrings{
Values: []string{url}, Values: []string{url},
Mode: models.RelationshipUpdateModeSet, Mode: models.RelationshipUpdateModeSet,
@@ -451,7 +467,9 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
models.Gallery{ models.Gallery{
ID: galleryIDs[galleryIdxWithImage], ID: galleryIDs[galleryIdxWithImage],
Title: title, Title: title,
Code: code,
Details: details, Details: details,
Photographer: photographer,
URLs: models.NewRelatedStrings([]string{url}), URLs: models.NewRelatedStrings([]string{url}),
Date: &date, Date: &date,
Rating: &rating, Rating: &rating,

View File

@@ -0,0 +1,2 @@
ALTER TABLE `galleries` ADD COLUMN `code` text;
ALTER TABLE `galleries` ADD COLUMN `photographer` text;

View File

@@ -24,7 +24,7 @@ export const GalleryDetailPanel: React.FC<IGalleryDetailProps> = ({
return ( return (
<> <>
<h6> <h6>
<FormattedMessage id="details" /> <FormattedMessage id="details" />:{" "}
</h6> </h6>
<p className="pre">{gallery.details}</p> <p className="pre">{gallery.details}</p>
</> </>
@@ -111,6 +111,16 @@ export const GalleryDetailPanel: React.FC<IGalleryDetailProps> = ({
<FormattedMessage id="updated_at" />:{" "} <FormattedMessage id="updated_at" />:{" "}
{TextUtils.formatDateTime(intl, gallery.updated_at)}{" "} {TextUtils.formatDateTime(intl, gallery.updated_at)}{" "}
</h6> </h6>
{gallery.code && (
<h6>
<FormattedMessage id="scene_code" />: {gallery.code}{" "}
</h6>
)}
{gallery.photographer && (
<h6>
<FormattedMessage id="photographer" />: {gallery.photographer}{" "}
</h6>
)}
</div> </div>
{gallery.studio && ( {gallery.studio && (
<div className="col-3 d-xl-none"> <div className="col-3 d-xl-none">

View File

@@ -86,8 +86,10 @@ export const GalleryEditPanel: React.FC<IProps> = ({
const schema = yup.object({ const schema = yup.object({
title: titleRequired ? yup.string().required() : yup.string().ensure(), title: titleRequired ? yup.string().required() : yup.string().ensure(),
code: yup.string().ensure(),
urls: yupUniqueStringList("urls"), urls: yupUniqueStringList("urls"),
date: yupDateString(intl), date: yupDateString(intl),
photographer: yup.string().ensure(),
rating100: yup.number().integer().nullable().defined(), rating100: yup.number().integer().nullable().defined(),
studio_id: yup.string().required().nullable(), studio_id: yup.string().required().nullable(),
performer_ids: yup.array(yup.string().required()).defined(), performer_ids: yup.array(yup.string().required()).defined(),
@@ -98,8 +100,10 @@ export const GalleryEditPanel: React.FC<IProps> = ({
const initialValues = { const initialValues = {
title: gallery?.title ?? "", title: gallery?.title ?? "",
code: gallery?.code ?? "",
urls: gallery?.urls ?? [], urls: gallery?.urls ?? [],
date: gallery?.date ?? "", date: gallery?.date ?? "",
photographer: gallery?.photographer ?? "",
rating100: gallery?.rating100 ?? null, rating100: gallery?.rating100 ?? null,
studio_id: gallery?.studio?.id ?? null, studio_id: gallery?.studio?.id ?? null,
performer_ids: (gallery?.performers ?? []).map((p) => p.id), performer_ids: (gallery?.performers ?? []).map((p) => p.id),
@@ -288,10 +292,18 @@ export const GalleryEditPanel: React.FC<IProps> = ({
formik.setFieldValue("title", galleryData.title); formik.setFieldValue("title", galleryData.title);
} }
if (galleryData.code) {
formik.setFieldValue("code", galleryData.code);
}
if (galleryData.details) { if (galleryData.details) {
formik.setFieldValue("details", galleryData.details); formik.setFieldValue("details", galleryData.details);
} }
if (galleryData.photographer) {
formik.setFieldValue("photographer", galleryData.photographer);
}
if (galleryData.date) { if (galleryData.date) {
formik.setFieldValue("date", galleryData.date); formik.setFieldValue("date", galleryData.date);
} }
@@ -490,6 +502,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
<Row className="form-container px-3"> <Row className="form-container px-3">
<Col lg={7} xl={12}> <Col lg={7} xl={12}>
{renderInputField("title")} {renderInputField("title")}
{renderInputField("code", "text", "scene_code")}
{renderURLListField( {renderURLListField(
"urls", "urls",
@@ -499,6 +512,7 @@ export const GalleryEditPanel: React.FC<IProps> = ({
)} )}
{renderDateField("date")} {renderDateField("date")}
{renderInputField("photographer")}
{renderRatingField("rating100", "rating")} {renderRatingField("rating100", "rating")}
{renderScenesField()} {renderScenesField()}

View File

@@ -48,6 +48,9 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
const [title, setTitle] = useState<ScrapeResult<string>>( const [title, setTitle] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(gallery.title, scraped.title) new ScrapeResult<string>(gallery.title, scraped.title)
); );
const [code, setCode] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(gallery.code, scraped.code)
);
const [urls, setURLs] = useState<ScrapeResult<string[]>>( const [urls, setURLs] = useState<ScrapeResult<string[]>>(
new ScrapeResult<string[]>( new ScrapeResult<string[]>(
gallery.urls, gallery.urls,
@@ -59,6 +62,9 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
const [date, setDate] = useState<ScrapeResult<string>>( const [date, setDate] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(gallery.date, scraped.date) new ScrapeResult<string>(gallery.date, scraped.date)
); );
const [photographer, setPhotographer] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(gallery.photographer, scraped.photographer)
);
const [studio, setStudio] = useState<ScrapeResult<string>>( const [studio, setStudio] = useState<ScrapeResult<string>>(
new ScrapeResult<string>(gallery.studio_id, scraped.studio?.stored_id) new ScrapeResult<string>(gallery.studio_id, scraped.studio?.stored_id)
); );
@@ -157,9 +163,17 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
// don't show the dialog if nothing was scraped // don't show the dialog if nothing was scraped
if ( 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 && !newStudio &&
newPerformers.length === 0 && newPerformers.length === 0 &&
newTags.length === 0 newTags.length === 0
@@ -173,8 +187,10 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
return { return {
title: title.getNewValue(), title: title.getNewValue(),
code: code.getNewValue(),
urls: urls.getNewValue(), urls: urls.getNewValue(),
date: date.getNewValue(), date: date.getNewValue(),
photographer: photographer.getNewValue(),
studio: newStudioValue studio: newStudioValue
? { ? {
stored_id: newStudioValue, stored_id: newStudioValue,
@@ -200,6 +216,11 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
result={title} result={title}
onChange={(value) => setTitle(value)} onChange={(value) => setTitle(value)}
/> />
<ScrapedInputGroupRow
title={intl.formatMessage({ id: "scene_code" })}
result={code}
onChange={(value) => setCode(value)}
/>
<ScrapedStringListRow <ScrapedStringListRow
title={intl.formatMessage({ id: "urls" })} title={intl.formatMessage({ id: "urls" })}
result={urls} result={urls}
@@ -211,6 +232,11 @@ export const GalleryScrapeDialog: React.FC<IGalleryScrapeDialogProps> = ({
result={date} result={date}
onChange={(value) => setDate(value)} onChange={(value) => setDate(value)}
/> />
<ScrapedInputGroupRow
title={intl.formatMessage({ id: "photographer" })}
result={photographer}
onChange={(value) => setPhotographer(value)}
/>
<ScrapedStudioRow <ScrapedStudioRow
title={intl.formatMessage({ id: "studios" })} title={intl.formatMessage({ id: "studios" })}
result={studio} result={studio}

View File

@@ -52,6 +52,7 @@
.gallery-tabs { .gallery-tabs {
max-height: calc(100vh - 4rem); max-height: calc(100vh - 4rem);
overflow: auto;
overflow-wrap: break-word; overflow-wrap: break-word;
word-wrap: break-word; word-wrap: break-word;
} }
@@ -62,7 +63,6 @@ $galleryTabWidth: 450px;
.gallery-tabs { .gallery-tabs {
flex: 0 0 $galleryTabWidth; flex: 0 0 $galleryTabWidth;
max-width: $galleryTabWidth; max-width: $galleryTabWidth;
overflow: auto;
&.collapsed { &.collapsed {
display: none; display: none;
@@ -285,3 +285,7 @@ $galleryTabWidth: 450px;
margin-bottom: 0; margin-bottom: 0;
} }
} }
.col-form-label {
padding-right: 2px;
}

View File

@@ -1160,6 +1160,7 @@
"updating_untagged_performers_description": "Updating untagged performers will try to match any performers that lack a stashid and update the metadata." "updating_untagged_performers_description": "Updating untagged performers will try to match any performers that lack a stashid and update the metadata."
}, },
"performers": "Performers", "performers": "Performers",
"photographer": "Photographer",
"piercings": "Piercings", "piercings": "Piercings",
"play_count": "Play Count", "play_count": "Play Count",
"play_duration": "Play Duration", "play_duration": "Play Duration",

View File

@@ -43,7 +43,9 @@ const displayModeOptions = [
const criterionOptions = [ const criterionOptions = [
createStringCriterionOption("title"), createStringCriterionOption("title"),
createStringCriterionOption("code", "scene_code"),
createStringCriterionOption("details"), createStringCriterionOption("details"),
createStringCriterionOption("photographer"),
PathCriterionOption, PathCriterionOption,
createStringCriterionOption("checksum", "media_info.checksum"), createStringCriterionOption("checksum", "media_info.checksum"),
RatingCriterionOption, RatingCriterionOption,

View File

@@ -195,5 +195,6 @@ export type CriterionType =
| "scene_updated_at" | "scene_updated_at"
| "description" | "description"
| "code" | "code"
| "photographer"
| "disambiguation" | "disambiguation"
| "has_chapters"; | "has_chapters";