mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Stash rating system (#2830)
* add rating100 fields to represent rating range 1-100 * deprecate existing (1-5) rating fields * add half- and quarter-star options for rating system * add decimal rating system option Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,7 @@ import (
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 39
|
||||
var appSchemaVersion uint = 40
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsBox embed.FS
|
||||
|
||||
@@ -543,6 +543,25 @@ func boolCriterionHandler(c *bool, column string, addJoinFn func(f *filterBuilde
|
||||
}
|
||||
}
|
||||
|
||||
func rating5CriterionHandler(c *models.IntCriterionInput, column string, addJoinFn func(f *filterBuilder)) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
// make a copy so we can adjust it
|
||||
cc := *c
|
||||
if cc.Value != 0 {
|
||||
cc.Value = models.Rating5To100(cc.Value)
|
||||
}
|
||||
if cc.Value2 != nil {
|
||||
val := models.Rating5To100(*cc.Value2)
|
||||
cc.Value2 = &val
|
||||
}
|
||||
|
||||
clause, args := getIntCriterionWhereClause(column, cc)
|
||||
f.addWhere(clause, args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dateCriterionHandler(c *models.DateCriterionInput, column string) criterionHandlerFunc {
|
||||
return func(ctx context.Context, f *filterBuilder) {
|
||||
if c != nil {
|
||||
|
||||
@@ -30,11 +30,12 @@ const (
|
||||
)
|
||||
|
||||
type galleryRow struct {
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
URL zero.String `db:"url"`
|
||||
Date models.SQLiteDate `db:"date"`
|
||||
Details zero.String `db:"details"`
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
URL zero.String `db:"url"`
|
||||
Date models.SQLiteDate `db:"date"`
|
||||
Details zero.String `db:"details"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Organized bool `db:"organized"`
|
||||
StudioID null.Int `db:"studio_id,omitempty"`
|
||||
@@ -651,7 +652,9 @@ func (qb *GalleryStore) makeFilter(ctx context.Context, galleryFilter *models.Ga
|
||||
|
||||
query.handleCriterion(ctx, qb.galleryPathCriterionHandler(galleryFilter.Path))
|
||||
query.handleCriterion(ctx, galleryFileCountCriterionHandler(qb, galleryFilter.FileCount))
|
||||
query.handleCriterion(ctx, intCriterionHandler(galleryFilter.Rating, "galleries.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(galleryFilter.Rating100, "galleries.rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(galleryFilter.Rating, "galleries.rating", nil))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(galleryFilter.URL, "galleries.url"))
|
||||
query.handleCriterion(ctx, boolCriterionHandler(galleryFilter.Organized, "galleries.organized", nil))
|
||||
query.handleCriterion(ctx, galleryIsMissingCriterionHandler(qb, galleryFilter.IsMissing))
|
||||
|
||||
@@ -54,7 +54,7 @@ func Test_galleryQueryBuilder_Create(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
url = "url"
|
||||
rating = 3
|
||||
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)
|
||||
@@ -205,7 +205,7 @@ func Test_galleryQueryBuilder_Update(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
url = "url"
|
||||
rating = 3
|
||||
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)
|
||||
@@ -399,7 +399,7 @@ func Test_galleryQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
title = "title"
|
||||
details = "details"
|
||||
url = "url"
|
||||
rating = 3
|
||||
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)
|
||||
|
||||
@@ -1547,7 +1547,7 @@ func TestGalleryQueryPathAndRating(t *testing.T) {
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
And: &models.GalleryFilterType{
|
||||
Rating: &models.IntCriterionInput{
|
||||
Rating100: &models.IntCriterionInput{
|
||||
Value: *galleryRating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
@@ -1588,7 +1588,7 @@ func TestGalleryQueryPathNotRating(t *testing.T) {
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Path: &pathCriterion,
|
||||
Not: &models.GalleryFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
Rating100: &ratingCriterion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1699,32 +1699,32 @@ func verifyGalleryQuery(t *testing.T, filter models.GalleryFilterType, verifyFn
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryRating(t *testing.T) {
|
||||
func TestGalleryQueryLegacyRating(t *testing.T) {
|
||||
const rating = 3
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyGalleriesRating(t, ratingCriterion)
|
||||
verifyGalleriesLegacyRating(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyGalleriesRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
func verifyGalleriesLegacyRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
@@ -1736,6 +1736,54 @@ func verifyGalleriesRating(t *testing.T, ratingCriterion models.IntCriterionInpu
|
||||
t.Errorf("Error querying gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
// convert criterion value to the 100 value
|
||||
ratingCriterion.Value = models.Rating5To100(ratingCriterion.Value)
|
||||
|
||||
for _, gallery := range galleries {
|
||||
verifyIntPtr(t, gallery.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryRating100(t *testing.T) {
|
||||
const rating = 60
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyGalleriesRating100(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyGalleriesRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Gallery
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
Rating100: &ratingCriterion,
|
||||
}
|
||||
|
||||
galleries, _, err := sqb.Query(ctx, &galleryFilter, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Error querying gallery: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, gallery := range galleries {
|
||||
verifyIntPtr(t, gallery.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
@@ -27,8 +27,9 @@ const (
|
||||
)
|
||||
|
||||
type imageRow struct {
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Title zero.String `db:"title"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Organized bool `db:"organized"`
|
||||
OCounter int `db:"o_counter"`
|
||||
@@ -632,7 +633,9 @@ func (qb *ImageStore) makeFilter(ctx context.Context, imageFilter *models.ImageF
|
||||
|
||||
query.handleCriterion(ctx, pathCriterionHandler(imageFilter.Path, "folders.path", "files.basename", qb.addFoldersTable))
|
||||
query.handleCriterion(ctx, imageFileCountCriterionHandler(qb, imageFilter.FileCount))
|
||||
query.handleCriterion(ctx, intCriterionHandler(imageFilter.Rating, "images.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(imageFilter.Rating100, "images.rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(imageFilter.Rating, "images.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(imageFilter.OCounter, "images.o_counter", nil))
|
||||
query.handleCriterion(ctx, boolCriterionHandler(imageFilter.Organized, "images.organized", nil))
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ func loadImageRelationships(ctx context.Context, expected models.Image, actual *
|
||||
func Test_imageQueryBuilder_Create(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -208,7 +208,7 @@ func makeImageFileWithID(i int) *file.ImageFile {
|
||||
func Test_imageQueryBuilder_Update(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -382,7 +382,7 @@ func clearImagePartial() models.ImagePartial {
|
||||
func Test_imageQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
var (
|
||||
title = "title"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -1595,7 +1595,7 @@ func TestImageQueryPathAndRating(t *testing.T) {
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
And: &models.ImageFilterType{
|
||||
Rating: &models.IntCriterionInput{
|
||||
Rating100: &models.IntCriterionInput{
|
||||
Value: int(imageRating.Int64),
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
@@ -1607,7 +1607,10 @@ func TestImageQueryPathAndRating(t *testing.T) {
|
||||
|
||||
images := queryImages(ctx, t, sqb, &imageFilter, nil)
|
||||
|
||||
assert.Len(t, images, 1)
|
||||
if !assert.Len(t, images, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
assert.Equal(t, imagePath, images[0].Path)
|
||||
assert.Equal(t, int(imageRating.Int64), *images[0].Rating)
|
||||
|
||||
@@ -1633,7 +1636,7 @@ func TestImageQueryPathNotRating(t *testing.T) {
|
||||
imageFilter := models.ImageFilterType{
|
||||
Path: &pathCriterion,
|
||||
Not: &models.ImageFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
Rating100: &ratingCriterion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1688,32 +1691,32 @@ func TestImageIllegalQuery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageQueryRating(t *testing.T) {
|
||||
func TestImageQueryLegacyRating(t *testing.T) {
|
||||
const rating = 3
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyImagesRating(t, ratingCriterion)
|
||||
verifyImagesLegacyRating(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
func verifyImagesLegacyRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
imageFilter := models.ImageFilterType{
|
||||
@@ -1725,6 +1728,54 @@ func verifyImagesRating(t *testing.T, ratingCriterion models.IntCriterionInput)
|
||||
t.Errorf("Error querying image: %s", err.Error())
|
||||
}
|
||||
|
||||
// convert criterion value to the 100 value
|
||||
ratingCriterion.Value = models.Rating5To100(ratingCriterion.Value)
|
||||
|
||||
for _, image := range images {
|
||||
verifyIntPtr(t, image.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageQueryRating100(t *testing.T) {
|
||||
const rating = 60
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyImagesRating100(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Image
|
||||
imageFilter := models.ImageFilterType{
|
||||
Rating100: &ratingCriterion,
|
||||
}
|
||||
|
||||
images, _, err := queryImagesWithCount(ctx, sqb, &imageFilter, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Error querying image: %s", err.Error())
|
||||
}
|
||||
|
||||
for _, image := range images {
|
||||
verifyIntPtr(t, image.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
6
pkg/sqlite/migrations/40_newratings.up.sql
Normal file
6
pkg/sqlite/migrations/40_newratings.up.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
UPDATE `scenes` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
UPDATE `galleries` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
UPDATE `images` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
UPDATE `movies` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
UPDATE `performers` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
UPDATE `studios` SET `rating` = (`rating` * 20) WHERE `rating` < 6;
|
||||
@@ -147,7 +147,9 @@ func (qb *movieQueryBuilder) makeFilter(ctx context.Context, movieFilter *models
|
||||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.Name, "movies.name"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.Director, "movies.director"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.Synopsis, "movies.synopsis"))
|
||||
query.handleCriterion(ctx, intCriterionHandler(movieFilter.Rating, "movies.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(movieFilter.Rating100, "movies.rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(movieFilter.Rating, "movies.rating", nil))
|
||||
query.handleCriterion(ctx, durationCriterionHandler(movieFilter.Duration, "movies.duration", nil))
|
||||
query.handleCriterion(ctx, movieIsMissingCriterionHandler(qb, movieFilter.IsMissing))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(movieFilter.URL, "movies.url"))
|
||||
|
||||
@@ -23,33 +23,34 @@ const performersTagsTable = "performers_tags"
|
||||
const performersImageTable = "performers_image" // performer cover image
|
||||
|
||||
type performerRow struct {
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Checksum string `db:"checksum"`
|
||||
Name zero.String `db:"name"`
|
||||
Gender zero.String `db:"gender"`
|
||||
URL zero.String `db:"url"`
|
||||
Twitter zero.String `db:"twitter"`
|
||||
Instagram zero.String `db:"instagram"`
|
||||
Birthdate models.SQLiteDate `db:"birthdate"`
|
||||
Ethnicity zero.String `db:"ethnicity"`
|
||||
Country zero.String `db:"country"`
|
||||
EyeColor zero.String `db:"eye_color"`
|
||||
Height null.Int `db:"height"`
|
||||
Measurements zero.String `db:"measurements"`
|
||||
FakeTits zero.String `db:"fake_tits"`
|
||||
CareerLength zero.String `db:"career_length"`
|
||||
Tattoos zero.String `db:"tattoos"`
|
||||
Piercings zero.String `db:"piercings"`
|
||||
Aliases zero.String `db:"aliases"`
|
||||
Favorite sql.NullBool `db:"favorite"`
|
||||
CreatedAt models.SQLiteTimestamp `db:"created_at"`
|
||||
UpdatedAt models.SQLiteTimestamp `db:"updated_at"`
|
||||
Rating null.Int `db:"rating"`
|
||||
Details zero.String `db:"details"`
|
||||
DeathDate models.SQLiteDate `db:"death_date"`
|
||||
HairColor zero.String `db:"hair_color"`
|
||||
Weight null.Int `db:"weight"`
|
||||
IgnoreAutoTag bool `db:"ignore_auto_tag"`
|
||||
ID int `db:"id" goqu:"skipinsert"`
|
||||
Checksum string `db:"checksum"`
|
||||
Name zero.String `db:"name"`
|
||||
Gender zero.String `db:"gender"`
|
||||
URL zero.String `db:"url"`
|
||||
Twitter zero.String `db:"twitter"`
|
||||
Instagram zero.String `db:"instagram"`
|
||||
Birthdate models.SQLiteDate `db:"birthdate"`
|
||||
Ethnicity zero.String `db:"ethnicity"`
|
||||
Country zero.String `db:"country"`
|
||||
EyeColor zero.String `db:"eye_color"`
|
||||
Height null.Int `db:"height"`
|
||||
Measurements zero.String `db:"measurements"`
|
||||
FakeTits zero.String `db:"fake_tits"`
|
||||
CareerLength zero.String `db:"career_length"`
|
||||
Tattoos zero.String `db:"tattoos"`
|
||||
Piercings zero.String `db:"piercings"`
|
||||
Aliases zero.String `db:"aliases"`
|
||||
Favorite sql.NullBool `db:"favorite"`
|
||||
CreatedAt models.SQLiteTimestamp `db:"created_at"`
|
||||
UpdatedAt models.SQLiteTimestamp `db:"updated_at"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Details zero.String `db:"details"`
|
||||
DeathDate models.SQLiteDate `db:"death_date"`
|
||||
HairColor zero.String `db:"hair_color"`
|
||||
Weight null.Int `db:"weight"`
|
||||
IgnoreAutoTag bool `db:"ignore_auto_tag"`
|
||||
}
|
||||
|
||||
func (r *performerRow) fromPerformer(o models.Performer) {
|
||||
@@ -90,27 +91,28 @@ func (r *performerRow) fromPerformer(o models.Performer) {
|
||||
|
||||
func (r *performerRow) resolve() *models.Performer {
|
||||
ret := &models.Performer{
|
||||
ID: r.ID,
|
||||
Checksum: r.Checksum,
|
||||
Name: r.Name.String,
|
||||
Gender: models.GenderEnum(r.Gender.String),
|
||||
URL: r.URL.String,
|
||||
Twitter: r.Twitter.String,
|
||||
Instagram: r.Instagram.String,
|
||||
Birthdate: r.Birthdate.DatePtr(),
|
||||
Ethnicity: r.Ethnicity.String,
|
||||
Country: r.Country.String,
|
||||
EyeColor: r.EyeColor.String,
|
||||
Height: nullIntPtr(r.Height),
|
||||
Measurements: r.Measurements.String,
|
||||
FakeTits: r.FakeTits.String,
|
||||
CareerLength: r.CareerLength.String,
|
||||
Tattoos: r.Tattoos.String,
|
||||
Piercings: r.Piercings.String,
|
||||
Aliases: r.Aliases.String,
|
||||
Favorite: r.Favorite.Bool,
|
||||
CreatedAt: r.CreatedAt.Timestamp,
|
||||
UpdatedAt: r.UpdatedAt.Timestamp,
|
||||
ID: r.ID,
|
||||
Checksum: r.Checksum,
|
||||
Name: r.Name.String,
|
||||
Gender: models.GenderEnum(r.Gender.String),
|
||||
URL: r.URL.String,
|
||||
Twitter: r.Twitter.String,
|
||||
Instagram: r.Instagram.String,
|
||||
Birthdate: r.Birthdate.DatePtr(),
|
||||
Ethnicity: r.Ethnicity.String,
|
||||
Country: r.Country.String,
|
||||
EyeColor: r.EyeColor.String,
|
||||
Height: nullIntPtr(r.Height),
|
||||
Measurements: r.Measurements.String,
|
||||
FakeTits: r.FakeTits.String,
|
||||
CareerLength: r.CareerLength.String,
|
||||
Tattoos: r.Tattoos.String,
|
||||
Piercings: r.Piercings.String,
|
||||
Aliases: r.Aliases.String,
|
||||
Favorite: r.Favorite.Bool,
|
||||
CreatedAt: r.CreatedAt.Timestamp,
|
||||
UpdatedAt: r.UpdatedAt.Timestamp,
|
||||
// expressed as 1-100
|
||||
Rating: nullIntPtr(r.Rating),
|
||||
Details: r.Details.String,
|
||||
DeathDate: r.DeathDate.DatePtr(),
|
||||
@@ -519,7 +521,9 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.CareerLength, tableName+".career_length"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Tattoos, tableName+".tattoos"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.Piercings, tableName+".piercings"))
|
||||
query.handleCriterion(ctx, intCriterionHandler(filter.Rating, tableName+".rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(filter.Rating100, tableName+".rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(filter.Rating, tableName+".rating", nil))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.HairColor, tableName+".hair_color"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(filter.URL, tableName+".url"))
|
||||
query.handleCriterion(ctx, intCriterionHandler(filter.Weight, tableName+".weight", nil))
|
||||
|
||||
@@ -440,7 +440,7 @@ func TestPerformerQueryEthnicityAndRating(t *testing.T) {
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
And: &models.PerformerFilterType{
|
||||
Rating: &models.IntCriterionInput{
|
||||
Rating100: &models.IntCriterionInput{
|
||||
Value: performerRating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
@@ -450,7 +450,10 @@ func TestPerformerQueryEthnicityAndRating(t *testing.T) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
performers := queryPerformers(ctx, t, &performerFilter, nil)
|
||||
|
||||
assert.Len(t, performers, 1)
|
||||
if !assert.Len(t, performers, 1) {
|
||||
return nil
|
||||
}
|
||||
|
||||
assert.Equal(t, performerEth, performers[0].Ethnicity)
|
||||
if assert.NotNil(t, performers[0].Rating) {
|
||||
assert.Equal(t, performerRating, *performers[0].Rating)
|
||||
@@ -478,7 +481,7 @@ func TestPerformerQueryEthnicityNotRating(t *testing.T) {
|
||||
performerFilter := models.PerformerFilterType{
|
||||
Ethnicity: ðCriterion,
|
||||
Not: &models.PerformerFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
Rating100: &ratingCriterion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1173,32 +1176,32 @@ func TestPerformerStashIDs(t *testing.T) {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
}
|
||||
func TestPerformerQueryRating(t *testing.T) {
|
||||
func TestPerformerQueryLegacyRating(t *testing.T) {
|
||||
const rating = 3
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyPerformersRating(t, ratingCriterion)
|
||||
verifyPerformersLegacyRating(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
func verifyPerformersLegacyRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
performerFilter := models.PerformerFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
@@ -1206,6 +1209,50 @@ func verifyPerformersRating(t *testing.T, ratingCriterion models.IntCriterionInp
|
||||
|
||||
performers := queryPerformers(ctx, t, &performerFilter, nil)
|
||||
|
||||
// convert criterion value to the 100 value
|
||||
ratingCriterion.Value = models.Rating5To100(ratingCriterion.Value)
|
||||
|
||||
for _, performer := range performers {
|
||||
verifyIntPtr(t, performer.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryRating100(t *testing.T) {
|
||||
const rating = 60
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyPerformersRating100(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
performerFilter := models.PerformerFilterType{
|
||||
Rating100: &ratingCriterion,
|
||||
}
|
||||
|
||||
performers := queryPerformers(ctx, t, &performerFilter, nil)
|
||||
|
||||
for _, performer := range performers {
|
||||
verifyIntPtr(t, performer.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
@@ -52,13 +52,14 @@ 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"`
|
||||
URL zero.String `db:"url"`
|
||||
Date models.SQLiteDate `db:"date"`
|
||||
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"`
|
||||
URL zero.String `db:"url"`
|
||||
Date models.SQLiteDate `db:"date"`
|
||||
// expressed as 1-100
|
||||
Rating null.Int `db:"rating"`
|
||||
Organized bool `db:"organized"`
|
||||
OCounter int `db:"o_counter"`
|
||||
@@ -844,7 +845,9 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
|
||||
}
|
||||
}))
|
||||
|
||||
query.handleCriterion(ctx, intCriterionHandler(sceneFilter.Rating, "scenes.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(sceneFilter.Rating100, "scenes.rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(sceneFilter.Rating, "scenes.rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(sceneFilter.OCounter, "scenes.o_counter", nil))
|
||||
query.handleCriterion(ctx, boolCriterionHandler(sceneFilter.Organized, "scenes.organized", nil))
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ func Test_sceneQueryBuilder_Create(t *testing.T) {
|
||||
details = "details"
|
||||
director = "director"
|
||||
url = "url"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -304,7 +304,7 @@ func Test_sceneQueryBuilder_Update(t *testing.T) {
|
||||
details = "details"
|
||||
director = "director"
|
||||
url = "url"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -512,7 +512,7 @@ func Test_sceneQueryBuilder_UpdatePartial(t *testing.T) {
|
||||
details = "details"
|
||||
director = "director"
|
||||
url = "url"
|
||||
rating = 3
|
||||
rating = 60
|
||||
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)
|
||||
@@ -2295,7 +2295,7 @@ func TestSceneQueryPathAndRating(t *testing.T) {
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
And: &models.SceneFilterType{
|
||||
Rating: &models.IntCriterionInput{
|
||||
Rating100: &models.IntCriterionInput{
|
||||
Value: sceneRating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
},
|
||||
@@ -2335,7 +2335,7 @@ func TestSceneQueryPathNotRating(t *testing.T) {
|
||||
sceneFilter := models.SceneFilterType{
|
||||
Path: &pathCriterion,
|
||||
Not: &models.SceneFilterType{
|
||||
Rating: &ratingCriterion,
|
||||
Rating100: &ratingCriterion,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2522,25 +2522,25 @@ func TestSceneQueryRating(t *testing.T) {
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyScenesRating(t, ratingCriterion)
|
||||
verifyScenesLegacyRating(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyScenesRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
func verifyScenesLegacyRating(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Scene
|
||||
sceneFilter := models.SceneFilterType{
|
||||
@@ -2549,6 +2549,51 @@ func verifyScenesRating(t *testing.T, ratingCriterion models.IntCriterionInput)
|
||||
|
||||
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
// convert criterion value to the 100 value
|
||||
ratingCriterion.Value = models.Rating5To100(ratingCriterion.Value)
|
||||
|
||||
for _, scene := range scenes {
|
||||
verifyIntPtr(t, scene.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestSceneQueryRating100(t *testing.T) {
|
||||
const rating = 60
|
||||
ratingCriterion := models.IntCriterionInput{
|
||||
Value: rating,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierIsNull
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
|
||||
ratingCriterion.Modifier = models.CriterionModifierNotNull
|
||||
verifyScenesRating100(t, ratingCriterion)
|
||||
}
|
||||
|
||||
func verifyScenesRating100(t *testing.T, ratingCriterion models.IntCriterionInput) {
|
||||
withTxn(func(ctx context.Context) error {
|
||||
sqb := db.Scene
|
||||
sceneFilter := models.SceneFilterType{
|
||||
Rating100: &ratingCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(ctx, t, sqb, &sceneFilter, nil)
|
||||
|
||||
for _, scene := range scenes {
|
||||
verifyIntPtr(t, scene.Rating, ratingCriterion)
|
||||
}
|
||||
|
||||
@@ -823,7 +823,7 @@ func getSceneTitle(index int) string {
|
||||
|
||||
func getRating(index int) sql.NullInt64 {
|
||||
rating := index % 6
|
||||
return sql.NullInt64{Int64: int64(rating), Valid: rating > 0}
|
||||
return sql.NullInt64{Int64: int64(rating * 20), Valid: rating > 0}
|
||||
}
|
||||
|
||||
func getIntPtr(r sql.NullInt64) *int {
|
||||
@@ -967,11 +967,13 @@ func makeScene(i int) *models.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
rating := getRating(i)
|
||||
|
||||
return &models.Scene{
|
||||
Title: title,
|
||||
Details: details,
|
||||
URL: getSceneEmptyString(i, urlField),
|
||||
Rating: getIntPtr(getRating(i)),
|
||||
Rating: getIntPtr(rating),
|
||||
OCounter: getOCounter(i),
|
||||
Date: getObjectDateObject(i),
|
||||
StudioID: studioID,
|
||||
|
||||
@@ -234,7 +234,9 @@ func (qb *studioQueryBuilder) makeFilter(ctx context.Context, studioFilter *mode
|
||||
query.handleCriterion(ctx, stringCriterionHandler(studioFilter.Name, studioTable+".name"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(studioFilter.Details, studioTable+".details"))
|
||||
query.handleCriterion(ctx, stringCriterionHandler(studioFilter.URL, studioTable+".url"))
|
||||
query.handleCriterion(ctx, intCriterionHandler(studioFilter.Rating, studioTable+".rating", nil))
|
||||
query.handleCriterion(ctx, intCriterionHandler(studioFilter.Rating100, studioTable+".rating", nil))
|
||||
// legacy rating handler
|
||||
query.handleCriterion(ctx, rating5CriterionHandler(studioFilter.Rating, studioTable+".rating", nil))
|
||||
query.handleCriterion(ctx, boolCriterionHandler(studioFilter.IgnoreAutoTag, studioTable+".ignore_auto_tag", nil))
|
||||
|
||||
query.handleCriterion(ctx, criterionHandlerFunc(func(ctx context.Context, f *filterBuilder) {
|
||||
|
||||
Reference in New Issue
Block a user