mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Add Details, Studio Code, and Photographer to Images (#4217)
* Add Details, Code, and Photographer to Images * Add date and details to image card --------- Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -13,10 +13,13 @@ import (
|
||||
// of cover image.
|
||||
func ToBasicJSON(image *models.Image) *jsonschema.Image {
|
||||
newImageJSON := jsonschema.Image{
|
||||
Title: image.Title,
|
||||
URLs: image.URLs.List(),
|
||||
CreatedAt: json.JSONTime{Time: image.CreatedAt},
|
||||
UpdatedAt: json.JSONTime{Time: image.UpdatedAt},
|
||||
Title: image.Title,
|
||||
Code: image.Code,
|
||||
URLs: image.URLs.List(),
|
||||
Details: image.Details,
|
||||
Photographer: image.Photographer,
|
||||
CreatedAt: json.JSONTime{Time: image.CreatedAt},
|
||||
UpdatedAt: json.JSONTime{Time: image.UpdatedAt},
|
||||
}
|
||||
|
||||
if image.Rating != nil {
|
||||
|
||||
@@ -76,6 +76,15 @@ func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image {
|
||||
if imageJSON.Title != "" {
|
||||
newImage.Title = imageJSON.Title
|
||||
}
|
||||
if imageJSON.Code != "" {
|
||||
newImage.Code = imageJSON.Code
|
||||
}
|
||||
if imageJSON.Details != "" {
|
||||
newImage.Details = imageJSON.Details
|
||||
}
|
||||
if imageJSON.Photographer != "" {
|
||||
newImage.Photographer = imageJSON.Photographer
|
||||
}
|
||||
if imageJSON.Rating != 0 {
|
||||
newImage.Rating = &imageJSON.Rating
|
||||
}
|
||||
|
||||
@@ -3,11 +3,14 @@ package models
|
||||
import "context"
|
||||
|
||||
type ImageFilterType struct {
|
||||
And *ImageFilterType `json:"AND"`
|
||||
Or *ImageFilterType `json:"OR"`
|
||||
Not *ImageFilterType `json:"NOT"`
|
||||
ID *IntCriterionInput `json:"id"`
|
||||
Title *StringCriterionInput `json:"title"`
|
||||
And *ImageFilterType `json:"AND"`
|
||||
Or *ImageFilterType `json:"OR"`
|
||||
Not *ImageFilterType `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
|
||||
|
||||
@@ -11,22 +11,25 @@ import (
|
||||
|
||||
type Image struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Studio string `json:"studio,omitempty"`
|
||||
Rating int `json:"rating,omitempty"`
|
||||
|
||||
// deprecated - for import only
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Organized bool `json:"organized,omitempty"`
|
||||
OCounter int `json:"o_counter,omitempty"`
|
||||
Galleries []GalleryRef `json:"galleries,omitempty"`
|
||||
Performers []string `json:"performers,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Files []string `json:"files,omitempty"`
|
||||
CreatedAt json.JSONTime `json:"created_at,omitempty"`
|
||||
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
|
||||
URLs []string `json:"urls,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Details string `json:"details,omitempty"`
|
||||
Photographer string `json:"photographer,omitempty"`
|
||||
Organized bool `json:"organized,omitempty"`
|
||||
OCounter int `json:"o_counter,omitempty"`
|
||||
Galleries []GalleryRef `json:"galleries,omitempty"`
|
||||
Performers []string `json:"performers,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Files []string `json:"files,omitempty"`
|
||||
CreatedAt json.JSONTime `json:"created_at,omitempty"`
|
||||
UpdatedAt json.JSONTime `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
func (s Image) Filename(basename string, hash string) string {
|
||||
|
||||
@@ -11,7 +11,10 @@ import (
|
||||
type Image struct {
|
||||
ID int `json:"id"`
|
||||
|
||||
Title string `json:"title"`
|
||||
Title string `json:"title"`
|
||||
Code string `json:"code"`
|
||||
Details string `json:"details"`
|
||||
Photographer string `json:"photographer"`
|
||||
// Rating expressed in 1-100 scale
|
||||
Rating *int `json:"rating"`
|
||||
Organized bool `json:"organized"`
|
||||
@@ -46,15 +49,18 @@ func NewImage() Image {
|
||||
|
||||
type ImagePartial struct {
|
||||
Title OptionalString
|
||||
Code OptionalString
|
||||
// Rating expressed in 1-100 scale
|
||||
Rating OptionalInt
|
||||
URLs *UpdateStrings
|
||||
Date OptionalDate
|
||||
Organized OptionalBool
|
||||
OCounter OptionalInt
|
||||
StudioID OptionalInt
|
||||
CreatedAt OptionalTime
|
||||
UpdatedAt OptionalTime
|
||||
Rating OptionalInt
|
||||
URLs *UpdateStrings
|
||||
Date OptionalDate
|
||||
Details OptionalString
|
||||
Photographer OptionalString
|
||||
Organized OptionalBool
|
||||
OCounter OptionalInt
|
||||
StudioID OptionalInt
|
||||
CreatedAt OptionalTime
|
||||
UpdatedAt OptionalTime
|
||||
|
||||
GalleryIDs *UpdateIDs
|
||||
TagIDs *UpdateIDs
|
||||
|
||||
@@ -33,7 +33,7 @@ const (
|
||||
dbConnTimeout = 30
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 53
|
||||
var appSchemaVersion uint = 54
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsBox embed.FS
|
||||
|
||||
@@ -31,21 +31,27 @@ const (
|
||||
type imageRow struct {
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
Code zero.String `db:"code"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Date NullDate `db:"date"`
|
||||
Organized bool `db:"organized"`
|
||||
OCounter int `db:"o_counter"`
|
||||
StudioID null.Int `db:"studio_id,omitempty"`
|
||||
CreatedAt Timestamp `db:"created_at"`
|
||||
UpdatedAt Timestamp `db:"updated_at"`
|
||||
Rating null.Int `db:"rating"`
|
||||
Date NullDate `db:"date"`
|
||||
Details zero.String `db:"details"`
|
||||
Photographer zero.String `db:"photographer"`
|
||||
Organized bool `db:"organized"`
|
||||
OCounter int `db:"o_counter"`
|
||||
StudioID null.Int `db:"studio_id,omitempty"`
|
||||
CreatedAt Timestamp `db:"created_at"`
|
||||
UpdatedAt Timestamp `db:"updated_at"`
|
||||
}
|
||||
|
||||
func (r *imageRow) fromImage(i models.Image) {
|
||||
r.ID = i.ID
|
||||
r.Title = zero.StringFrom(i.Title)
|
||||
r.Code = zero.StringFrom(i.Code)
|
||||
r.Rating = intFromPtr(i.Rating)
|
||||
r.Date = NullDateFromDatePtr(i.Date)
|
||||
r.Details = zero.StringFrom(i.Details)
|
||||
r.Photographer = zero.StringFrom(i.Photographer)
|
||||
r.Organized = i.Organized
|
||||
r.OCounter = i.OCounter
|
||||
r.StudioID = intFromPtr(i.StudioID)
|
||||
@@ -63,13 +69,16 @@ type imageQueryRow struct {
|
||||
|
||||
func (r *imageQueryRow) resolve() *models.Image {
|
||||
ret := &models.Image{
|
||||
ID: r.ID,
|
||||
Title: r.Title.String,
|
||||
Rating: nullIntPtr(r.Rating),
|
||||
Date: r.Date.DatePtr(),
|
||||
Organized: r.Organized,
|
||||
OCounter: r.OCounter,
|
||||
StudioID: nullIntPtr(r.StudioID),
|
||||
ID: r.ID,
|
||||
Title: r.Title.String,
|
||||
Code: r.Code.String,
|
||||
Rating: nullIntPtr(r.Rating),
|
||||
Date: r.Date.DatePtr(),
|
||||
Details: r.Details.String,
|
||||
Photographer: r.Photographer.String,
|
||||
Organized: r.Organized,
|
||||
OCounter: r.OCounter,
|
||||
StudioID: nullIntPtr(r.StudioID),
|
||||
|
||||
PrimaryFileID: nullIntFileIDPtr(r.PrimaryFileID),
|
||||
Checksum: r.PrimaryFileChecksum.String,
|
||||
@@ -91,8 +100,11 @@ type imageRowRecord struct {
|
||||
|
||||
func (r *imageRowRecord) fromPartial(i models.ImagePartial) {
|
||||
r.setNullString("title", i.Title)
|
||||
r.setNullString("code", i.Code)
|
||||
r.setNullInt("rating", i.Rating)
|
||||
r.setNullDate("date", i.Date)
|
||||
r.setNullString("details", i.Details)
|
||||
r.setNullString("photographer", i.Photographer)
|
||||
r.setBool("organized", i.Organized)
|
||||
r.setInt("o_counter", i.OCounter)
|
||||
r.setNullInt("studio_id", i.StudioID)
|
||||
@@ -672,6 +684,9 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
|
||||
stringCriterionHandler(imageFilter.Checksum, "fingerprints_md5.fingerprint")(ctx, f)
|
||||
}))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Title, "images.title"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Code, "images.code"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Details, "images.details"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(imageFilter.Photographer, "images.photographer"))
|
||||
|
||||
query.handleCriterion(ctx, pathCriterionHandler(imageFilter.Path, "folders.path", "files.basename", qb.addFoldersTable))
|
||||
query.handleCriterion(ctx, imageFileCountCriterionHandler(qb, imageFilter.FileCount))
|
||||
|
||||
@@ -57,13 +57,16 @@ func loadImageRelationships(ctx context.Context, expected models.Image, actual *
|
||||
|
||||
func Test_imageQueryBuilder_Create(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 60
|
||||
ocounter = 5
|
||||
url = "url"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
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"
|
||||
rating = 60
|
||||
details = "details"
|
||||
photographer = "photographer"
|
||||
ocounter = 5
|
||||
url = "url"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
imageFile = makeFileWithID(fileIdxStartImageFiles)
|
||||
)
|
||||
@@ -77,8 +80,11 @@ func Test_imageQueryBuilder_Create(t *testing.T) {
|
||||
"full",
|
||||
models.Image{
|
||||
Title: title,
|
||||
Code: code,
|
||||
Rating: &rating,
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
@@ -94,13 +100,16 @@ func Test_imageQueryBuilder_Create(t *testing.T) {
|
||||
{
|
||||
"with file",
|
||||
models.Image{
|
||||
Title: title,
|
||||
Rating: &rating,
|
||||
Date: &date,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
StudioID: &studioIDs[studioIdxWithImage],
|
||||
Title: title,
|
||||
Code: code,
|
||||
Rating: &rating,
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
StudioID: &studioIDs[studioIdxWithImage],
|
||||
Files: models.NewRelatedFiles([]models.File{
|
||||
imageFile.(*models.ImageFile),
|
||||
}),
|
||||
@@ -214,13 +223,16 @@ func makeImageFileWithID(i int) *models.ImageFile {
|
||||
|
||||
func Test_imageQueryBuilder_Update(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 60
|
||||
url = "url"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
ocounter = 5
|
||||
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"
|
||||
rating = 60
|
||||
url = "url"
|
||||
details = "details"
|
||||
photographer = "photographer"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
ocounter = 5
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -233,9 +245,12 @@ func Test_imageQueryBuilder_Update(t *testing.T) {
|
||||
&models.Image{
|
||||
ID: imageIDs[imageIdxWithGallery],
|
||||
Title: title,
|
||||
Code: code,
|
||||
Rating: &rating,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
StudioID: &studioIDs[studioIdxWithImage],
|
||||
@@ -382,6 +397,9 @@ func clearImagePartial() models.ImagePartial {
|
||||
// leave mandatory fields
|
||||
return models.ImagePartial{
|
||||
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},
|
||||
Rating: models.OptionalInt{Set: true, Null: true},
|
||||
URLs: &models.UpdateStrings{Mode: models.RelationshipUpdateModeSet},
|
||||
Date: models.OptionalDate{Set: true, Null: true},
|
||||
@@ -394,13 +412,16 @@ func clearImagePartial() models.ImagePartial {
|
||||
|
||||
func Test_imageQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 60
|
||||
url = "url"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
ocounter = 5
|
||||
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"
|
||||
rating = 60
|
||||
url = "url"
|
||||
date, _ = models.ParseDate("2003-02-01")
|
||||
ocounter = 5
|
||||
createdAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
updatedAt = time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@@ -414,8 +435,11 @@ func Test_imageQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
"full",
|
||||
imageIDs[imageIdx1WithGallery],
|
||||
models.ImagePartial{
|
||||
Title: models.NewOptionalString(title),
|
||||
Rating: models.NewOptionalInt(rating),
|
||||
Title: models.NewOptionalString(title),
|
||||
Code: models.NewOptionalString(code),
|
||||
Details: models.NewOptionalString(details),
|
||||
Photographer: models.NewOptionalString(photographer),
|
||||
Rating: models.NewOptionalInt(rating),
|
||||
URLs: &models.UpdateStrings{
|
||||
Values: []string{url},
|
||||
Mode: models.RelationshipUpdateModeSet,
|
||||
@@ -440,14 +464,17 @@ func Test_imageQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
},
|
||||
},
|
||||
models.Image{
|
||||
ID: imageIDs[imageIdx1WithGallery],
|
||||
Title: title,
|
||||
Rating: &rating,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
StudioID: &studioIDs[studioIdxWithImage],
|
||||
ID: imageIDs[imageIdx1WithGallery],
|
||||
Title: title,
|
||||
Code: code,
|
||||
Details: details,
|
||||
Photographer: photographer,
|
||||
Rating: &rating,
|
||||
URLs: models.NewRelatedStrings([]string{url}),
|
||||
Date: &date,
|
||||
Organized: true,
|
||||
OCounter: ocounter,
|
||||
StudioID: &studioIDs[studioIdxWithImage],
|
||||
Files: models.NewRelatedFiles([]models.File{
|
||||
makeImageFile(imageIdx1WithGallery),
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE `images` ADD COLUMN `code` text;
|
||||
ALTER TABLE `images` ADD COLUMN `photographer` text;
|
||||
ALTER TABLE `images` ADD COLUMN `details` text;
|
||||
Reference in New Issue
Block a user