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:
skier233
2022-11-15 17:31:44 -05:00
committed by GitHub
parent f66333bac9
commit 7eae751d1c
133 changed files with 2192 additions and 761 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;

View File

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

View File

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

View File

@@ -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: &ethCriterion,
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)
}

View File

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

View File

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

View File

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

View File

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