mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
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:
@@ -13,11 +13,13 @@ import (
|
||||
// does not convert the relationships to other objects.
|
||||
func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) {
|
||||
newGalleryJSON := jsonschema.Gallery{
|
||||
Title: gallery.Title,
|
||||
URLs: gallery.URLs.List(),
|
||||
Details: gallery.Details,
|
||||
CreatedAt: json.JSONTime{Time: gallery.CreatedAt},
|
||||
UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt},
|
||||
Title: gallery.Title,
|
||||
Code: gallery.Code,
|
||||
URLs: gallery.URLs.List(),
|
||||
Details: gallery.Details,
|
||||
Photographer: gallery.Photographer,
|
||||
CreatedAt: json.JSONTime{Time: gallery.CreatedAt},
|
||||
UpdatedAt: json.JSONTime{Time: gallery.UpdatedAt},
|
||||
}
|
||||
|
||||
if gallery.FolderID != nil {
|
||||
|
||||
@@ -62,9 +62,15 @@ func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.G
|
||||
if galleryJSON.Title != "" {
|
||||
newGallery.Title = galleryJSON.Title
|
||||
}
|
||||
if galleryJSON.Code != "" {
|
||||
newGallery.Code = galleryJSON.Code
|
||||
}
|
||||
if galleryJSON.Details != "" {
|
||||
newGallery.Details = galleryJSON.Details
|
||||
}
|
||||
if galleryJSON.Photographer != "" {
|
||||
newGallery.Photographer = galleryJSON.Photographer
|
||||
}
|
||||
if len(galleryJSON.URLs) > 0 {
|
||||
newGallery.URLs = models.NewRelatedStrings(galleryJSON.URLs)
|
||||
} else if galleryJSON.URL != "" {
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package models
|
||||
|
||||
type GalleryFilterType struct {
|
||||
And *GalleryFilterType `json:"AND"`
|
||||
Or *GalleryFilterType `json:"OR"`
|
||||
Not *GalleryFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
Details *StringCriterionInput `json:"details"`
|
||||
And *GalleryFilterType `json:"AND"`
|
||||
Or *GalleryFilterType `json:"OR"`
|
||||
Not *GalleryFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
Code *StringCriterionInput `json:"code"`
|
||||
Details *StringCriterionInput `json:"details"`
|
||||
Photographer *StringCriterionInput `json:"photographer"`
|
||||
// Filter by file checksum
|
||||
Checksum *StringCriterionInput `json:"checksum"`
|
||||
// Filter by path
|
||||
@@ -57,9 +59,11 @@ type GalleryUpdateInput struct {
|
||||
ClientMutationID *string `json:"clientMutationId"`
|
||||
ID string `json:"id"`
|
||||
Title *string `json:"title"`
|
||||
Code *string `json:"code"`
|
||||
Urls []string `json:"urls"`
|
||||
Date *string `json:"date"`
|
||||
Details *string `json:"details"`
|
||||
Photographer *string `json:"photographer"`
|
||||
Rating100 *int `json:"rating100"`
|
||||
Organized *bool `json:"organized"`
|
||||
SceneIds []string `json:"scene_ids"`
|
||||
|
||||
@@ -18,20 +18,22 @@ type GalleryChapter struct {
|
||||
}
|
||||
|
||||
type Gallery struct {
|
||||
ZipFiles []string `json:"zip_files,omitempty"`
|
||||
FolderPath string `json:"folder_path,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
Rating int `json:"rating,omitempty"`
|
||||
Organized bool `json:"organized,omitempty"`
|
||||
Chapters []GalleryChapter `json:"chapters,omitempty"`
|
||||
Studio string `json:"studio,omitempty"`
|
||||
Performers []string `json:"performers,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedAt json.JSONTime `json:"created_at,omitempty"`
|
||||
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
|
||||
ZipFiles []string `json:"zip_files,omitempty"`
|
||||
FolderPath string `json:"folder_path,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
Photographer string `json:"photographer,omitempty"`
|
||||
Rating int `json:"rating,omitempty"`
|
||||
Organized bool `json:"organized,omitempty"`
|
||||
Chapters []GalleryChapter `json:"chapters,omitempty"`
|
||||
Studio string `json:"studio,omitempty"`
|
||||
Performers []string `json:"performers,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedAt json.JSONTime `json:"created_at,omitempty"`
|
||||
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
|
||||
|
||||
// deprecated - for import only
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
@@ -10,9 +10,11 @@ import (
|
||||
type Gallery struct {
|
||||
ID int `json:"id"`
|
||||
|
||||
Title string `json:"title"`
|
||||
Date *Date `json:"date"`
|
||||
Details string `json:"details"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Date *Date `json:"date"`
|
||||
Details string `json:"details"`
|
||||
Photographer string `json:"photographer"`
|
||||
// Rating expressed in 1-100 scale
|
||||
Rating *int `json:"rating"`
|
||||
Organized bool `json:"organized"`
|
||||
@@ -50,10 +52,12 @@ type GalleryPartial struct {
|
||||
// Path OptionalString
|
||||
// Checksum OptionalString
|
||||
// Zip OptionalBool
|
||||
Title OptionalString
|
||||
URLs *UpdateStrings
|
||||
Date OptionalDate
|
||||
Details OptionalString
|
||||
Title OptionalString
|
||||
Code OptionalString
|
||||
URLs *UpdateStrings
|
||||
Date OptionalDate
|
||||
Details OptionalString
|
||||
Photographer OptionalString
|
||||
// Rating expressed in 1-100 scale
|
||||
Rating OptionalInt
|
||||
Organized OptionalBool
|
||||
|
||||
@@ -3,13 +3,15 @@ package scraper
|
||||
import "github.com/stashapp/stash/pkg/models"
|
||||
|
||||
type ScrapedGallery struct {
|
||||
Title *string `json:"title"`
|
||||
Details *string `json:"details"`
|
||||
URLs []string `json:"urls"`
|
||||
Date *string `json:"date"`
|
||||
Studio *models.ScrapedStudio `json:"studio"`
|
||||
Tags []*models.ScrapedTag `json:"tags"`
|
||||
Performers []*models.ScrapedPerformer `json:"performers"`
|
||||
Title *string `json:"title"`
|
||||
Code *string `json:"code"`
|
||||
Details *string `json:"details"`
|
||||
Photographer *string `json:"photographer"`
|
||||
URLs []string `json:"urls"`
|
||||
Date *string `json:"date"`
|
||||
Studio *models.ScrapedStudio `json:"studio"`
|
||||
Tags []*models.ScrapedTag `json:"tags"`
|
||||
Performers []*models.ScrapedPerformer `json:"performers"`
|
||||
|
||||
// deprecated
|
||||
URL *string `json:"url"`
|
||||
@@ -18,10 +20,12 @@ type ScrapedGallery struct {
|
||||
func (ScrapedGallery) IsScrapedContent() {}
|
||||
|
||||
type ScrapedGalleryInput struct {
|
||||
Title *string `json:"title"`
|
||||
Details *string `json:"details"`
|
||||
URLs []string `json:"urls"`
|
||||
Date *string `json:"date"`
|
||||
Title *string `json:"title"`
|
||||
Code *string `json:"code"`
|
||||
Details *string `json:"details"`
|
||||
Photographer *string `json:"photographer"`
|
||||
URLs []string `json:"urls"`
|
||||
Date *string `json:"date"`
|
||||
|
||||
// deprecated
|
||||
URL *string `json:"url"`
|
||||
|
||||
@@ -972,11 +972,12 @@ func (s mappedScraper) scrapeScenes(ctx context.Context, q mappedQuery) ([]*Scra
|
||||
|
||||
func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*ScrapedScene, error) {
|
||||
sceneScraperConfig := s.Scene
|
||||
sceneMap := sceneScraperConfig.mappedConfig
|
||||
if sceneMap == nil {
|
||||
if sceneScraperConfig == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
sceneMap := sceneScraperConfig.mappedConfig
|
||||
|
||||
logger.Debug(`Processing scene:`)
|
||||
results := sceneMap.process(ctx, q, s.Common)
|
||||
|
||||
@@ -1000,11 +1001,12 @@ func (s mappedScraper) scrapeGallery(ctx context.Context, q mappedQuery) (*Scrap
|
||||
var ret ScrapedGallery
|
||||
|
||||
galleryScraperConfig := s.Gallery
|
||||
galleryMap := galleryScraperConfig.mappedConfig
|
||||
if galleryMap == nil {
|
||||
if galleryScraperConfig == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
galleryMap := galleryScraperConfig.mappedConfig
|
||||
|
||||
galleryPerformersMap := galleryScraperConfig.Performers
|
||||
galleryTagsMap := galleryScraperConfig.Tags
|
||||
galleryStudioMap := galleryScraperConfig.Studio
|
||||
@@ -1062,11 +1064,12 @@ func (s mappedScraper) scrapeMovie(ctx context.Context, q mappedQuery) (*models.
|
||||
var ret models.ScrapedMovie
|
||||
|
||||
movieScraperConfig := s.Movie
|
||||
movieMap := movieScraperConfig.mappedConfig
|
||||
if movieMap == nil {
|
||||
if movieScraperConfig == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
movieMap := movieScraperConfig.mappedConfig
|
||||
|
||||
movieStudioMap := movieScraperConfig.Studio
|
||||
|
||||
results := movieMap.process(ctx, q, s.Common)
|
||||
|
||||
@@ -33,7 +33,7 @@ const (
|
||||
dbConnTimeout = 30
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 52
|
||||
var appSchemaVersion uint = 53
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsBox embed.FS
|
||||
|
||||
@@ -31,10 +31,12 @@ const (
|
||||
)
|
||||
|
||||
type galleryRow struct {
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
Date NullDate `db:"date"`
|
||||
Details zero.String `db:"details"`
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
Code zero.String `db:"code"`
|
||||
Date NullDate `db:"date"`
|
||||
Details zero.String `db:"details"`
|
||||
Photographer zero.String `db:"photographer"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Organized bool `db:"organized"`
|
||||
@@ -47,8 +49,10 @@ type galleryRow struct {
|
||||
func (r *galleryRow) fromGallery(o models.Gallery) {
|
||||
r.ID = o.ID
|
||||
r.Title = zero.StringFrom(o.Title)
|
||||
r.Code = zero.StringFrom(o.Code)
|
||||
r.Date = NullDateFromDatePtr(o.Date)
|
||||
r.Details = zero.StringFrom(o.Details)
|
||||
r.Photographer = zero.StringFrom(o.Photographer)
|
||||
r.Rating = intFromPtr(o.Rating)
|
||||
r.Organized = o.Organized
|
||||
r.StudioID = intFromPtr(o.StudioID)
|
||||
@@ -70,8 +74,10 @@ func (r *galleryQueryRow) resolve() *models.Gallery {
|
||||
ret := &models.Gallery{
|
||||
ID: r.ID,
|
||||
Title: r.Title.String,
|
||||
Code: r.Code.String,
|
||||
Date: r.Date.DatePtr(),
|
||||
Details: r.Details.String,
|
||||
Photographer: r.Photographer.String,
|
||||
Rating: nullIntPtr(r.Rating),
|
||||
Organized: r.Organized,
|
||||
StudioID: nullIntPtr(r.StudioID),
|
||||
@@ -96,8 +102,10 @@ type galleryRowRecord struct {
|
||||
|
||||
func (r *galleryRowRecord) fromPartial(o models.GalleryPartial) {
|
||||
r.setNullString("title", o.Title)
|
||||
r.setNullString("code", o.Code)
|
||||
r.setNullDate("date", o.Date)
|
||||
r.setNullString("details", o.Details)
|
||||
r.setNullString("photographer", o.Photographer)
|
||||
r.setNullInt("rating", o.Rating)
|
||||
r.setBool("organized", o.Organized)
|
||||
r.setNullInt("studio_id", o.StudioID)
|
||||
@@ -655,7 +663,9 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
|
||||
|
||||
query.handleCriterion(ctx, intCriterionHandler(galleryFilter.ID, "galleries.id", nil))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Title, "galleries.title"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Code, "galleries.code"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Details, "galleries.details"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.Photographer, "galleries.photographer"))
|
||||
|
||||
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
if galleryFilter.Checksum != nil {
|
||||
|
||||
@@ -56,12 +56,14 @@ func loadGalleryRelationships(ctx context.Context, expected models.Gallery, actu
|
||||
|
||||
func Test_galleryQueryBuilder_Create(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
url = "url"
|
||||
rating = 60
|
||||
details = "details"
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
title = "title"
|
||||
code = "1337"
|
||||
url = "url"
|
||||
rating = 60
|
||||
details = "details"
|
||||
photographer = "photographer"
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
galleryFile = makeFileWithID(fileIdxStartGalleryFiles)
|
||||
)
|
||||
@@ -77,9 +79,11 @@ func Test_galleryQueryBuilder_Create(t *testing.T) {
|
||||
"full",
|
||||
models.Gallery{
|
||||
Title: title,
|
||||
Code: code,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithScene],
|
||||
@@ -94,13 +98,15 @@ func Test_galleryQueryBuilder_Create(t *testing.T) {
|
||||
{
|
||||
"with file",
|
||||
models.Gallery{
|
||||
Title: title,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithScene],
|
||||
Title: title,
|
||||
Code: code,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithScene],
|
||||
Files: models.NewRelatedFiles([]models.File{
|
||||
galleryFile,
|
||||
}),
|
||||
@@ -207,12 +213,14 @@ func makeGalleryFileWithID(i int) *models.BaseFile {
|
||||
|
||||
func Test_galleryQueryBuilder_Update(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
url = "url"
|
||||
rating = 60
|
||||
details = "details"
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
title = "title"
|
||||
code = "code"
|
||||
url = "url"
|
||||
rating = 60
|
||||
details = "details"
|
||||
photographer = "photographer"
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
date, _ := models.ParseDate("2003-02-01")
|
||||
@@ -225,14 +233,16 @@ func Test_galleryQueryBuilder_Update(t *testing.T) {
|
||||
{
|
||||
"full",
|
||||
&models.Gallery{
|
||||
ID: galleryIDs[galleryIdxWithScene],
|
||||
Title: title,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithScene],
|
||||
ID: galleryIDs[galleryIdxWithScene],
|
||||
Title: title,
|
||||
Code: code,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithScene],
|
||||
Files: models.NewRelatedFiles([]models.File{
|
||||
makeGalleryFileWithID(galleryIdxWithScene),
|
||||
}),
|
||||
@@ -389,7 +399,9 @@ func clearGalleryPartial() models.GalleryPartial {
|
||||
// leave mandatory fields
|
||||
return models.GalleryPartial{
|
||||
Title: models.OptionalString{Set: true, Null: true},
|
||||
Code: models.OptionalString{Set: true, Null: true},
|
||||
Details: models.OptionalString{Set: true, Null: true},
|
||||
Photographer: models.OptionalString{Set: true, Null: true},
|
||||
URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet},
|
||||
Date: models.OptionalDate{Set: true, Null: true},
|
||||
Rating: models.OptionalInt{Set: true, Null: true},
|
||||
@@ -401,12 +413,14 @@ func clearGalleryPartial() models.GalleryPartial {
|
||||
|
||||
func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
details = "details"
|
||||
url = "url"
|
||||
rating = 60
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
title = "title"
|
||||
code = "code"
|
||||
details = "details"
|
||||
photographer = "photographer"
|
||||
url = "url"
|
||||
rating = 60
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
)
|
||||
@@ -422,8 +436,10 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
"full",
|
||||
galleryIDs[galleryIdxWithImage],
|
||||
models.GalleryPartial{
|
||||
Title: models.NewOptionalString(title),
|
||||
Details: models.NewOptionalString(details),
|
||||
Title: models.NewOptionalString(title),
|
||||
Code: models.NewOptionalString(code),
|
||||
Details: models.NewOptionalString(details),
|
||||
Photographer: models.NewOptionalString(photographer),
|
||||
URLs: &models.UpdateStrings{
|
||||
Values: []string{url},
|
||||
Mode: models.RelationshipUpdateModeSet,
|
||||
@@ -449,14 +465,16 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
},
|
||||
},
|
||||
models.Gallery{
|
||||
ID: galleryIDs[galleryIdxWithImage],
|
||||
Title: title,
|
||||
Details: details,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithGallery],
|
||||
ID: galleryIDs[galleryIdxWithImage],
|
||||
Title: title,
|
||||
Code: code,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Rating: &rating,
|
||||
Organized: true,
|
||||
StudioID: &studioIDs[studioIdxWithGallery],
|
||||
Files: models.NewRelatedFiles([]models.File{
|
||||
makeGalleryFile(galleryIdxWithImage),
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `galleries` ADD COLUMN `code` text;
|
||||
ALTER TABLE `galleries` ADD COLUMN `photographer` text;
|
||||
Reference in New Issue
Block a user