mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
Join count filter criteria (#1254)
Co-authored-by: mrbrdo <mrbrdo@gmail.com> Co-authored-by: peolic <66393006+peolic@users.noreply.github.com>
This commit is contained in:
@@ -47,7 +47,7 @@ input PerformerFilterType {
|
||||
measurements: StringCriterionInput
|
||||
"""Filter by fake tits value"""
|
||||
fake_tits: StringCriterionInput
|
||||
"""Filter by career length"""
|
||||
"""Filter by career length"""
|
||||
career_length: StringCriterionInput
|
||||
"""Filter by tattoos"""
|
||||
tattoos: StringCriterionInput
|
||||
@@ -61,6 +61,14 @@ input PerformerFilterType {
|
||||
is_missing: String
|
||||
"""Filter to only include performers with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter by scene count"""
|
||||
scene_count: IntCriterionInput
|
||||
"""Filter by image count"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by gallery count"""
|
||||
gallery_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: String
|
||||
"""Filter by url"""
|
||||
@@ -82,7 +90,7 @@ input SceneFilterType {
|
||||
AND: SceneFilterType
|
||||
OR: SceneFilterType
|
||||
NOT: SceneFilterType
|
||||
|
||||
|
||||
"""Filter by path"""
|
||||
path: StringCriterionInput
|
||||
"""Filter by rating"""
|
||||
@@ -105,10 +113,14 @@ input SceneFilterType {
|
||||
movies: MultiCriterionInput
|
||||
"""Filter to only include scenes with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include scenes with performers with these tags"""
|
||||
performer_tags: MultiCriterionInput
|
||||
"""Filter to only include scenes with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter by StashID"""
|
||||
stash_id: String
|
||||
"""Filter by url"""
|
||||
@@ -152,10 +164,14 @@ input GalleryFilterType {
|
||||
studios: MultiCriterionInput
|
||||
"""Filter to only include galleries with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include galleries with performers with these tags"""
|
||||
performer_tags: MultiCriterionInput
|
||||
"""Filter to only include galleries with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter by number of images in this gallery"""
|
||||
image_count: IntCriterionInput
|
||||
"""Filter by url"""
|
||||
@@ -203,10 +219,14 @@ input ImageFilterType {
|
||||
studios: MultiCriterionInput
|
||||
"""Filter to only include images with these tags"""
|
||||
tags: MultiCriterionInput
|
||||
"""Filter by tag count"""
|
||||
tag_count: IntCriterionInput
|
||||
"""Filter to only include images with performers with these tags"""
|
||||
performer_tags: MultiCriterionInput
|
||||
"""Filter to only include images with these performers"""
|
||||
performers: MultiCriterionInput
|
||||
"""Filter by performer count"""
|
||||
performer_count: IntCriterionInput
|
||||
"""Filter to only include images with these galleries"""
|
||||
galleries: MultiCriterionInput
|
||||
}
|
||||
|
||||
@@ -405,3 +405,23 @@ func (m *multiCriterionHandlerBuilder) handler(criterion *models.MultiCriterionI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type countCriterionHandlerBuilder struct {
|
||||
primaryTable string
|
||||
joinTable string
|
||||
primaryFK string
|
||||
}
|
||||
|
||||
func (m *countCriterionHandlerBuilder) handler(criterion *models.IntCriterionInput) criterionHandlerFunc {
|
||||
return func(f *filterBuilder) {
|
||||
if criterion != nil {
|
||||
clause, count := getCountCriterionClause(m.primaryTable, m.joinTable, m.primaryFK, *criterion)
|
||||
|
||||
if count == 1 {
|
||||
f.addWhere(clause, criterion.Value)
|
||||
} else {
|
||||
f.addWhere(clause)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +239,16 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if tagCountFilter := galleryFilter.TagCount; tagCountFilter != nil {
|
||||
clause, count := getCountCriterionClause(galleryTable, galleriesTagsTable, galleryIDColumn, *tagCountFilter)
|
||||
|
||||
if count == 1 {
|
||||
query.addArg(tagCountFilter.Value)
|
||||
}
|
||||
|
||||
query.addWhere(clause)
|
||||
}
|
||||
|
||||
if performersFilter := galleryFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 {
|
||||
for _, performerID := range performersFilter.Value {
|
||||
query.addArg(performerID)
|
||||
@@ -250,6 +260,16 @@ func (qb *galleryQueryBuilder) Query(galleryFilter *models.GalleryFilterType, fi
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if performerCountFilter := galleryFilter.PerformerCount; performerCountFilter != nil {
|
||||
clause, count := getCountCriterionClause(galleryTable, performersGalleriesTable, galleryIDColumn, *performerCountFilter)
|
||||
|
||||
if count == 1 {
|
||||
query.addArg(performerCountFilter.Value)
|
||||
}
|
||||
|
||||
query.addWhere(clause)
|
||||
}
|
||||
|
||||
if studiosFilter := galleryFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
||||
for _, studioID := range studiosFilter.Value {
|
||||
query.addArg(studioID)
|
||||
@@ -382,7 +402,15 @@ func (qb *galleryQueryBuilder) getGallerySort(findFilter *models.FindFilterType)
|
||||
sort = findFilter.GetSort("path")
|
||||
direction = findFilter.GetDirection()
|
||||
}
|
||||
return getSort(sort, direction, "galleries")
|
||||
|
||||
switch sort {
|
||||
case "tag_count":
|
||||
return getCountSort(galleryTable, galleriesTagsTable, galleryIDColumn, direction)
|
||||
case "performer_count":
|
||||
return getCountSort(galleryTable, performersGalleriesTable, galleryIDColumn, direction)
|
||||
default:
|
||||
return getSort(sort, direction, "galleries")
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *galleryQueryBuilder) queryGallery(query string, args []interface{}) (*models.Gallery, error) {
|
||||
|
||||
@@ -630,6 +630,88 @@ func TestGalleryQueryPerformerTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryTagCount(t *testing.T) {
|
||||
const tagCount = 1
|
||||
tagCountCriterion := models.IntCriterionInput{
|
||||
Value: tagCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyGalleriesTagCount(t, tagCountCriterion)
|
||||
}
|
||||
|
||||
func verifyGalleriesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
TagCount: &tagCountCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||
assert.Greater(t, len(galleries), 0)
|
||||
|
||||
for _, gallery := range galleries {
|
||||
ids, err := sqb.GetTagIDs(gallery.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), tagCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestGalleryQueryPerformerCount(t *testing.T) {
|
||||
const performerCount = 1
|
||||
performerCountCriterion := models.IntCriterionInput{
|
||||
Value: performerCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyGalleriesPerformerCount(t, performerCountCriterion)
|
||||
}
|
||||
|
||||
func verifyGalleriesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Gallery()
|
||||
galleryFilter := models.GalleryFilterType{
|
||||
PerformerCount: &performerCountCriterion,
|
||||
}
|
||||
|
||||
galleries := queryGallery(t, sqb, &galleryFilter, nil)
|
||||
assert.Greater(t, len(galleries), 0)
|
||||
|
||||
for _, gallery := range galleries {
|
||||
ids, err := sqb.GetPerformerIDs(gallery.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), performerCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TODO Count
|
||||
// TODO All
|
||||
// TODO Query
|
||||
|
||||
@@ -328,6 +328,16 @@ func (qb *imageQueryBuilder) Query(imageFilter *models.ImageFilterType, findFilt
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if tagCountFilter := imageFilter.TagCount; tagCountFilter != nil {
|
||||
clause, count := getCountCriterionClause(imageTable, imagesTagsTable, imageIDColumn, *tagCountFilter)
|
||||
|
||||
if count == 1 {
|
||||
query.addArg(tagCountFilter.Value)
|
||||
}
|
||||
|
||||
query.addWhere(clause)
|
||||
}
|
||||
|
||||
if galleriesFilter := imageFilter.Galleries; galleriesFilter != nil && len(galleriesFilter.Value) > 0 {
|
||||
for _, galleryID := range galleriesFilter.Value {
|
||||
query.addArg(galleryID)
|
||||
@@ -350,6 +360,16 @@ func (qb *imageQueryBuilder) Query(imageFilter *models.ImageFilterType, findFilt
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
if performerCountFilter := imageFilter.PerformerCount; performerCountFilter != nil {
|
||||
clause, count := getCountCriterionClause(imageTable, performersImagesTable, imageIDColumn, *performerCountFilter)
|
||||
|
||||
if count == 1 {
|
||||
query.addArg(performerCountFilter.Value)
|
||||
}
|
||||
|
||||
query.addWhere(clause)
|
||||
}
|
||||
|
||||
if studiosFilter := imageFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 {
|
||||
for _, studioID := range studiosFilter.Value {
|
||||
query.addArg(studioID)
|
||||
@@ -412,7 +432,15 @@ func (qb *imageQueryBuilder) getImageSort(findFilter *models.FindFilterType) str
|
||||
}
|
||||
sort := findFilter.GetSort("title")
|
||||
direction := findFilter.GetDirection()
|
||||
return getSort(sort, direction, "images")
|
||||
|
||||
switch sort {
|
||||
case "tag_count":
|
||||
return getCountSort(imageTable, imagesTagsTable, imageIDColumn, direction)
|
||||
case "performer_count":
|
||||
return getCountSort(imageTable, performersImagesTable, imageIDColumn, direction)
|
||||
default:
|
||||
return getSort(sort, direction, "images")
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *imageQueryBuilder) queryImage(query string, args []interface{}) (*models.Image, error) {
|
||||
|
||||
@@ -683,6 +683,88 @@ func TestImageQueryPerformerTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageQueryTagCount(t *testing.T) {
|
||||
const tagCount = 1
|
||||
tagCountCriterion := models.IntCriterionInput{
|
||||
Value: tagCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesTagCount(t, tagCountCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Image()
|
||||
imageFilter := models.ImageFilterType{
|
||||
TagCount: &tagCountCriterion,
|
||||
}
|
||||
|
||||
images := queryImages(t, sqb, &imageFilter, nil)
|
||||
assert.Greater(t, len(images), 0)
|
||||
|
||||
for _, image := range images {
|
||||
ids, err := sqb.GetTagIDs(image.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), tagCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageQueryPerformerCount(t *testing.T) {
|
||||
const performerCount = 1
|
||||
performerCountCriterion := models.IntCriterionInput{
|
||||
Value: performerCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyImagesPerformerCount(t, performerCountCriterion)
|
||||
}
|
||||
|
||||
func verifyImagesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Image()
|
||||
imageFilter := models.ImageFilterType{
|
||||
PerformerCount: &performerCountCriterion,
|
||||
}
|
||||
|
||||
images := queryImages(t, sqb, &imageFilter, nil)
|
||||
assert.Greater(t, len(images), 0)
|
||||
|
||||
for _, image := range images {
|
||||
ids, err := sqb.GetPerformerIDs(image.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), performerCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestImageQuerySorting(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sort := titleField
|
||||
|
||||
@@ -271,6 +271,11 @@ func (qb *performerQueryBuilder) Query(performerFilter *models.PerformerFilterTy
|
||||
query.addHaving(havingClause)
|
||||
}
|
||||
|
||||
query.handleCountCriterion(performerFilter.TagCount, performerTable, performersTagsTable, performerIDColumn)
|
||||
query.handleCountCriterion(performerFilter.SceneCount, performerTable, performersScenesTable, performerIDColumn)
|
||||
query.handleCountCriterion(performerFilter.ImageCount, performerTable, performersImagesTable, performerIDColumn)
|
||||
query.handleCountCriterion(performerFilter.GalleryCount, performerTable, performersGalleriesTable, performerIDColumn)
|
||||
|
||||
query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter)
|
||||
idsResult, countResult, err := query.executeFind()
|
||||
if err != nil {
|
||||
@@ -370,6 +375,11 @@ func (qb *performerQueryBuilder) getPerformerSort(findFilter *models.FindFilterT
|
||||
sort = findFilter.GetSort("name")
|
||||
direction = findFilter.GetDirection()
|
||||
}
|
||||
|
||||
if sort == "tag_count" {
|
||||
return getCountSort(performerTable, performersTagsTable, performerIDColumn, direction)
|
||||
}
|
||||
|
||||
return getSort(sort, direction, "performers")
|
||||
}
|
||||
|
||||
|
||||
@@ -387,6 +387,188 @@ func TestPerformerQueryTags(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryTagCount(t *testing.T) {
|
||||
const tagCount = 1
|
||||
tagCountCriterion := models.IntCriterionInput{
|
||||
Value: tagCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersTagCount(t, tagCountCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Performer()
|
||||
performerFilter := models.PerformerFilterType{
|
||||
TagCount: &tagCountCriterion,
|
||||
}
|
||||
|
||||
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||
assert.Greater(t, len(performers), 0)
|
||||
|
||||
for _, performer := range performers {
|
||||
ids, err := sqb.GetTagIDs(performer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), tagCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQuerySceneCount(t *testing.T) {
|
||||
const sceneCount = 1
|
||||
sceneCountCriterion := models.IntCriterionInput{
|
||||
Value: sceneCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||
|
||||
sceneCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||
|
||||
sceneCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||
|
||||
sceneCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersSceneCount(t, sceneCountCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersSceneCount(t *testing.T, sceneCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Performer()
|
||||
performerFilter := models.PerformerFilterType{
|
||||
SceneCount: &sceneCountCriterion,
|
||||
}
|
||||
|
||||
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||
assert.Greater(t, len(performers), 0)
|
||||
|
||||
for _, performer := range performers {
|
||||
ids, err := r.Scene().FindByPerformerID(performer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), sceneCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryImageCount(t *testing.T) {
|
||||
const imageCount = 1
|
||||
imageCountCriterion := models.IntCriterionInput{
|
||||
Value: imageCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersImageCount(t, imageCountCriterion)
|
||||
|
||||
imageCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersImageCount(t, imageCountCriterion)
|
||||
|
||||
imageCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersImageCount(t, imageCountCriterion)
|
||||
|
||||
imageCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersImageCount(t, imageCountCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersImageCount(t *testing.T, imageCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Performer()
|
||||
performerFilter := models.PerformerFilterType{
|
||||
ImageCount: &imageCountCriterion,
|
||||
}
|
||||
|
||||
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||
assert.Greater(t, len(performers), 0)
|
||||
|
||||
for _, performer := range performers {
|
||||
pp := 0
|
||||
|
||||
_, count, err := r.Image().Query(&models.ImageFilterType{
|
||||
Performers: &models.MultiCriterionInput{
|
||||
Value: []string{strconv.Itoa(performer.ID)},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
}, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, count, imageCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerQueryGalleryCount(t *testing.T) {
|
||||
const galleryCount = 1
|
||||
galleryCountCriterion := models.IntCriterionInput{
|
||||
Value: galleryCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||
|
||||
galleryCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||
|
||||
galleryCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||
|
||||
galleryCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyPerformersGalleryCount(t, galleryCountCriterion)
|
||||
}
|
||||
|
||||
func verifyPerformersGalleryCount(t *testing.T, galleryCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Performer()
|
||||
performerFilter := models.PerformerFilterType{
|
||||
GalleryCount: &galleryCountCriterion,
|
||||
}
|
||||
|
||||
performers := queryPerformers(t, sqb, &performerFilter, nil)
|
||||
assert.Greater(t, len(performers), 0)
|
||||
|
||||
for _, performer := range performers {
|
||||
pp := 0
|
||||
|
||||
_, count, err := r.Gallery().Query(&models.GalleryFilterType{
|
||||
Performers: &models.MultiCriterionInput{
|
||||
Value: []string{strconv.Itoa(performer.ID)},
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
},
|
||||
}, &models.FindFilterType{
|
||||
PerPage: &pp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, count, galleryCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestPerformerStashIDs(t *testing.T) {
|
||||
if err := withTxn(func(r models.Repository) error {
|
||||
qb := r.Performer()
|
||||
|
||||
@@ -151,3 +151,15 @@ func (qb *queryBuilder) handleStringCriterionInput(c *models.StringCriterionInpu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *queryBuilder) handleCountCriterion(countFilter *models.IntCriterionInput, primaryTable, joinTable, primaryFK string) {
|
||||
if countFilter != nil {
|
||||
clause, count := getCountCriterionClause(primaryTable, joinTable, primaryFK, *countFilter)
|
||||
|
||||
if count == 1 {
|
||||
qb.addArg(countFilter.Value)
|
||||
}
|
||||
|
||||
qb.addWhere(clause)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +286,7 @@ func (qb *sceneQueryBuilder) Wall(q *string) ([]*models.Scene, error) {
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) All() ([]*models.Scene, error) {
|
||||
return qb.queryScenes(selectAll(sceneTable)+qb.getSceneSort(nil), nil)
|
||||
return qb.queryScenes(selectAll(sceneTable)+qb.getDefaultSceneSort(), nil)
|
||||
}
|
||||
|
||||
func illegalFilterCombination(type1, type2 string) error {
|
||||
@@ -348,7 +348,9 @@ func (qb *sceneQueryBuilder) makeFilter(sceneFilter *models.SceneFilterType) *fi
|
||||
query.handleCriterionFunc(stringCriterionHandler(sceneFilter.URL, "scenes.url"))
|
||||
|
||||
query.handleCriterionFunc(sceneTagsCriterionHandler(qb, sceneFilter.Tags))
|
||||
query.handleCriterionFunc(sceneTagCountCriterionHandler(qb, sceneFilter.TagCount))
|
||||
query.handleCriterionFunc(scenePerformersCriterionHandler(qb, sceneFilter.Performers))
|
||||
query.handleCriterionFunc(scenePerformerCountCriterionHandler(qb, sceneFilter.PerformerCount))
|
||||
query.handleCriterionFunc(sceneStudioCriterionHandler(qb, sceneFilter.Studios))
|
||||
query.handleCriterionFunc(sceneMoviesCriterionHandler(qb, sceneFilter.Movies))
|
||||
query.handleCriterionFunc(sceneStashIDsHandler(qb, sceneFilter.StashID))
|
||||
@@ -384,7 +386,8 @@ func (qb *sceneQueryBuilder) Query(sceneFilter *models.SceneFilterType, findFilt
|
||||
|
||||
query.addFilter(filter)
|
||||
|
||||
query.sortAndPagination = qb.getSceneSort(findFilter) + getPagination(findFilter)
|
||||
qb.setSceneSort(&query, findFilter)
|
||||
query.sortAndPagination += getPagination(findFilter)
|
||||
|
||||
idsResult, countResult, err := query.executeFind()
|
||||
if err != nil {
|
||||
@@ -520,6 +523,7 @@ func (qb *sceneQueryBuilder) getMultiCriterionHandlerBuilder(foreignTable, joinT
|
||||
addJoinsFunc: addJoinsFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
qb.tagsRepository().join(f, "tags_join", "scenes.id")
|
||||
@@ -530,6 +534,16 @@ func sceneTagsCriterionHandler(qb *sceneQueryBuilder, tags *models.MultiCriterio
|
||||
return h.handler(tags)
|
||||
}
|
||||
|
||||
func sceneTagCountCriterionHandler(qb *sceneQueryBuilder, tagCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||
h := countCriterionHandlerBuilder{
|
||||
primaryTable: sceneTable,
|
||||
joinTable: scenesTagsTable,
|
||||
primaryFK: sceneIDColumn,
|
||||
}
|
||||
|
||||
return h.handler(tagCount)
|
||||
}
|
||||
|
||||
func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
qb.performersRepository().join(f, "performers_join", "scenes.id")
|
||||
@@ -540,6 +554,16 @@ func scenePerformersCriterionHandler(qb *sceneQueryBuilder, performers *models.M
|
||||
return h.handler(performers)
|
||||
}
|
||||
|
||||
func scenePerformerCountCriterionHandler(qb *sceneQueryBuilder, performerCount *models.IntCriterionInput) criterionHandlerFunc {
|
||||
h := countCriterionHandlerBuilder{
|
||||
primaryTable: sceneTable,
|
||||
joinTable: performersScenesTable,
|
||||
primaryFK: sceneIDColumn,
|
||||
}
|
||||
|
||||
return h.handler(performerCount)
|
||||
}
|
||||
|
||||
func sceneStudioCriterionHandler(qb *sceneQueryBuilder, studios *models.MultiCriterionInput) criterionHandlerFunc {
|
||||
addJoinsFunc := func(f *filterBuilder) {
|
||||
f.addJoin("studios", "studio", "studio.id = scenes.studio_id")
|
||||
@@ -586,8 +610,8 @@ func scenePerformerTagsCriterionHandler(qb *sceneQueryBuilder, performerTagsFilt
|
||||
f.addWhere("performer_tags_join.tag_id IN "+getInBinding(len(performerTagsFilter.Value)), args...)
|
||||
f.addHaving(fmt.Sprintf("count(distinct performer_tags_join.tag_id) IS %d", len(performerTagsFilter.Value)))
|
||||
} else if performerTagsFilter.Modifier == models.CriterionModifierExcludes {
|
||||
f.addWhere(fmt.Sprintf(`not exists
|
||||
(select performers_scenes.performer_id from performers_scenes
|
||||
f.addWhere(fmt.Sprintf(`not exists
|
||||
(select performers_scenes.performer_id from performers_scenes
|
||||
left join performers_tags on performers_tags.performer_id = performers_scenes.performer_id where
|
||||
performers_scenes.scene_id = scenes.id AND
|
||||
performers_tags.tag_id in %s)`, getInBinding(len(performerTagsFilter.Value))), args...)
|
||||
@@ -612,8 +636,8 @@ func handleScenePerformerTagsCriterion(query *queryBuilder, performerTagsFilter
|
||||
query.addWhere("performer_tags_join.tag_id IN " + getInBinding(len(performerTagsFilter.Value)))
|
||||
query.addHaving(fmt.Sprintf("count(distinct performer_tags_join.tag_id) IS %d", len(performerTagsFilter.Value)))
|
||||
} else if performerTagsFilter.Modifier == models.CriterionModifierExcludes {
|
||||
query.addWhere(fmt.Sprintf(`not exists
|
||||
(select performers_scenes.performer_id from performers_scenes
|
||||
query.addWhere(fmt.Sprintf(`not exists
|
||||
(select performers_scenes.performer_id from performers_scenes
|
||||
left join performers_tags on performers_tags.performer_id = performers_scenes.performer_id where
|
||||
performers_scenes.scene_id = scenes.id AND
|
||||
performers_tags.tag_id in %s)`, getInBinding(len(performerTagsFilter.Value))))
|
||||
@@ -621,13 +645,25 @@ func handleScenePerformerTagsCriterion(query *queryBuilder, performerTagsFilter
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) getSceneSort(findFilter *models.FindFilterType) string {
|
||||
func (qb *sceneQueryBuilder) getDefaultSceneSort() string {
|
||||
return " ORDER BY scenes.path, scenes.date ASC "
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) setSceneSort(query *queryBuilder, findFilter *models.FindFilterType) {
|
||||
if findFilter == nil {
|
||||
return " ORDER BY scenes.path, scenes.date ASC "
|
||||
query.sortAndPagination += qb.getDefaultSceneSort()
|
||||
return
|
||||
}
|
||||
sort := findFilter.GetSort("title")
|
||||
direction := findFilter.GetDirection()
|
||||
return getSort(sort, direction, "scenes")
|
||||
switch sort {
|
||||
case "tag_count":
|
||||
query.sortAndPagination += getCountSort(sceneTable, scenesTagsTable, sceneIDColumn, direction)
|
||||
case "performer_count":
|
||||
query.sortAndPagination += getCountSort(sceneTable, performersScenesTable, sceneIDColumn, direction)
|
||||
default:
|
||||
query.sortAndPagination += getSort(sort, direction, "scenes")
|
||||
}
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) queryScene(query string, args []interface{}) (*models.Scene, error) {
|
||||
|
||||
@@ -1214,6 +1214,88 @@ func TestSceneQueryPagination(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSceneQueryTagCount(t *testing.T) {
|
||||
const tagCount = 1
|
||||
tagCountCriterion := models.IntCriterionInput{
|
||||
Value: tagCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyScenesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyScenesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyScenesTagCount(t, tagCountCriterion)
|
||||
|
||||
tagCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyScenesTagCount(t, tagCountCriterion)
|
||||
}
|
||||
|
||||
func verifyScenesTagCount(t *testing.T, tagCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Scene()
|
||||
sceneFilter := models.SceneFilterType{
|
||||
TagCount: &tagCountCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(t, sqb, &sceneFilter, nil)
|
||||
assert.Greater(t, len(scenes), 0)
|
||||
|
||||
for _, scene := range scenes {
|
||||
ids, err := sqb.GetTagIDs(scene.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), tagCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestSceneQueryPerformerCount(t *testing.T) {
|
||||
const performerCount = 1
|
||||
performerCountCriterion := models.IntCriterionInput{
|
||||
Value: performerCount,
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
}
|
||||
|
||||
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierNotEquals
|
||||
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierGreaterThan
|
||||
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||
|
||||
performerCountCriterion.Modifier = models.CriterionModifierLessThan
|
||||
verifyScenesPerformerCount(t, performerCountCriterion)
|
||||
}
|
||||
|
||||
func verifyScenesPerformerCount(t *testing.T, performerCountCriterion models.IntCriterionInput) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Scene()
|
||||
sceneFilter := models.SceneFilterType{
|
||||
PerformerCount: &performerCountCriterion,
|
||||
}
|
||||
|
||||
scenes := queryScene(t, sqb, &sceneFilter, nil)
|
||||
assert.Greater(t, len(scenes), 0)
|
||||
|
||||
for _, scene := range scenes {
|
||||
ids, err := sqb.GetPerformerIDs(scene.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
verifyInt(t, len(ids), performerCountCriterion)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestSceneCountByTagID(t *testing.T) {
|
||||
withTxn(func(r models.Repository) error {
|
||||
sqb := r.Scene()
|
||||
|
||||
@@ -24,6 +24,8 @@ const (
|
||||
sceneIdxWithMovie = iota
|
||||
sceneIdxWithGallery
|
||||
sceneIdxWithPerformer
|
||||
sceneIdx1WithPerformer
|
||||
sceneIdx2WithPerformer
|
||||
sceneIdxWithTwoPerformers
|
||||
sceneIdxWithTag
|
||||
sceneIdxWithTwoTags
|
||||
@@ -40,6 +42,8 @@ const (
|
||||
const (
|
||||
imageIdxWithGallery = iota
|
||||
imageIdxWithPerformer
|
||||
imageIdx1WithPerformer
|
||||
imageIdx2WithPerformer
|
||||
imageIdxWithTwoPerformers
|
||||
imageIdxWithTag
|
||||
imageIdxWithTwoTags
|
||||
@@ -55,12 +59,15 @@ const (
|
||||
performerIdxWithScene = iota
|
||||
performerIdx1WithScene
|
||||
performerIdx2WithScene
|
||||
performerIdxWithTwoScenes
|
||||
performerIdxWithImage
|
||||
performerIdxWithTwoImages
|
||||
performerIdx1WithImage
|
||||
performerIdx2WithImage
|
||||
performerIdxWithTag
|
||||
performerIdxWithTwoTags
|
||||
performerIdxWithGallery
|
||||
performerIdxWithTwoGalleries
|
||||
performerIdx1WithGallery
|
||||
performerIdx2WithGallery
|
||||
// new indexes above
|
||||
@@ -87,6 +94,8 @@ const (
|
||||
galleryIdxWithScene = iota
|
||||
galleryIdxWithImage
|
||||
galleryIdxWithPerformer
|
||||
galleryIdx1WithPerformer
|
||||
galleryIdx2WithPerformer
|
||||
galleryIdxWithTwoPerformers
|
||||
galleryIdxWithTag
|
||||
galleryIdxWithTwoTags
|
||||
@@ -185,6 +194,8 @@ var (
|
||||
{sceneIdxWithTwoPerformers, performerIdx2WithScene},
|
||||
{sceneIdxWithPerformerTag, performerIdxWithTag},
|
||||
{sceneIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||
{sceneIdx1WithPerformer, performerIdxWithTwoScenes},
|
||||
{sceneIdx2WithPerformer, performerIdxWithTwoScenes},
|
||||
}
|
||||
|
||||
sceneGalleryLinks = [][2]int{
|
||||
@@ -218,6 +229,8 @@ var (
|
||||
{imageIdxWithTwoPerformers, performerIdx2WithImage},
|
||||
{imageIdxWithPerformerTag, performerIdxWithTag},
|
||||
{imageIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||
{imageIdx1WithPerformer, performerIdxWithTwoImages},
|
||||
{imageIdx2WithPerformer, performerIdxWithTwoImages},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -228,6 +241,8 @@ var (
|
||||
{galleryIdxWithTwoPerformers, performerIdx2WithGallery},
|
||||
{galleryIdxWithPerformerTag, performerIdxWithTag},
|
||||
{galleryIdxWithPerformerTwoTags, performerIdxWithTwoTags},
|
||||
{galleryIdx1WithPerformer, performerIdxWithTwoGalleries},
|
||||
{galleryIdx2WithPerformer, performerIdxWithTwoGalleries},
|
||||
}
|
||||
|
||||
galleryTagLinks = [][2]int{
|
||||
|
||||
@@ -2,6 +2,7 @@ package sqlite
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -44,10 +45,15 @@ func getPaginationSQL(page int, perPage int) string {
|
||||
return " LIMIT " + strconv.Itoa(perPage) + " OFFSET " + strconv.Itoa(page) + " "
|
||||
}
|
||||
|
||||
func getSort(sort string, direction string, tableName string) string {
|
||||
func getSortDirection(direction string) string {
|
||||
if direction != "ASC" && direction != "DESC" {
|
||||
direction = "ASC"
|
||||
return "ASC"
|
||||
} else {
|
||||
return direction
|
||||
}
|
||||
}
|
||||
func getSort(sort string, direction string, tableName string) string {
|
||||
direction = getSortDirection(direction)
|
||||
|
||||
const randomSeedPrefix = "random_"
|
||||
|
||||
@@ -96,6 +102,10 @@ func getRandomSort(tableName string, direction string, seed float64) string {
|
||||
return " ORDER BY " + "(substr(" + colName + " * " + randomSortString + ", length(" + colName + ") + 2))" + " " + direction
|
||||
}
|
||||
|
||||
func getCountSort(primaryTable, joinTable, primaryFK, direction string) string {
|
||||
return fmt.Sprintf(" ORDER BY (SELECT COUNT(*) FROM %s WHERE %s = %s.id) %s", joinTable, primaryFK, primaryTable, getSortDirection(direction))
|
||||
}
|
||||
|
||||
func getSearchBinding(columns []string, q string, not bool) (string, []interface{}) {
|
||||
var likeClauses []string
|
||||
var args []interface{}
|
||||
@@ -213,6 +223,11 @@ func getMultiCriterionClause(primaryTable, foreignTable, joinTable, primaryFK, f
|
||||
return whereClause, havingClause
|
||||
}
|
||||
|
||||
func getCountCriterionClause(primaryTable, joinTable, primaryFK string, criterion models.IntCriterionInput) (string, int) {
|
||||
lhs := fmt.Sprintf("(SELECT COUNT(*) FROM %s s WHERE s.%s = %s.id)", joinTable, primaryFK, primaryTable)
|
||||
return getIntCriterionWhereClause(lhs, criterion)
|
||||
}
|
||||
|
||||
func ensureTx(tx *sqlx.Tx) {
|
||||
if tx == nil {
|
||||
panic("must use a transaction")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Added scene queue.
|
||||
|
||||
### 🎨 Improvements
|
||||
* Add various `count` filter criteria and sort options.
|
||||
* Scroll to top when changing page number.
|
||||
* Add URL filter criteria for scenes, galleries, movies, performers and studios.
|
||||
* Add HTTP endpoint for health checking at `/healthz`.
|
||||
|
||||
@@ -25,6 +25,7 @@ export type CriterionType =
|
||||
| "tags"
|
||||
| "sceneTags"
|
||||
| "performerTags"
|
||||
| "tag_count"
|
||||
| "performers"
|
||||
| "studios"
|
||||
| "movies"
|
||||
@@ -90,6 +91,8 @@ export abstract class Criterion {
|
||||
return "Scene Tags";
|
||||
case "performerTags":
|
||||
return "Performer Tags";
|
||||
case "tag_count":
|
||||
return "Tag Count";
|
||||
case "performers":
|
||||
return "Performers";
|
||||
case "studios":
|
||||
@@ -358,6 +361,15 @@ export class NumberCriterion extends Criterion {
|
||||
}
|
||||
}
|
||||
|
||||
export class MandatoryNumberCriterion extends NumberCriterion {
|
||||
public modifierOptions = [
|
||||
Criterion.getModifierOption(CriterionModifier.Equals),
|
||||
Criterion.getModifierOption(CriterionModifier.NotEquals),
|
||||
Criterion.getModifierOption(CriterionModifier.GreaterThan),
|
||||
Criterion.getModifierOption(CriterionModifier.LessThan),
|
||||
];
|
||||
}
|
||||
|
||||
export class DurationCriterion extends Criterion {
|
||||
public type: CriterionType;
|
||||
public parameterName: string;
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
/* eslint-disable consistent-return, default-case */
|
||||
import { CriterionModifier } from "src/core/generated-graphql";
|
||||
import {
|
||||
Criterion,
|
||||
CriterionType,
|
||||
StringCriterion,
|
||||
NumberCriterion,
|
||||
DurationCriterion,
|
||||
MandatoryStringCriterion,
|
||||
MandatoryNumberCriterion,
|
||||
} from "./criterion";
|
||||
import { OrganizedCriterion } from "./organized";
|
||||
import { FavoriteCriterion } from "./favorite";
|
||||
@@ -46,7 +45,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
case "image_count":
|
||||
case "gallery_count":
|
||||
case "performer_count":
|
||||
return new NumberCriterion(type, type);
|
||||
case "tag_count":
|
||||
return new MandatoryNumberCriterion(type, type);
|
||||
case "resolution":
|
||||
return new ResolutionCriterion();
|
||||
case "average_resolution":
|
||||
@@ -89,17 +89,8 @@ export function makeCriteria(type: CriterionType = "none") {
|
||||
return new GalleriesCriterion();
|
||||
case "birth_year":
|
||||
return new NumberCriterion(type, type);
|
||||
case "age": {
|
||||
const ret = new NumberCriterion(type, type);
|
||||
// null/not null doesn't make sense for these criteria
|
||||
ret.modifierOptions = [
|
||||
Criterion.getModifierOption(CriterionModifier.Equals),
|
||||
Criterion.getModifierOption(CriterionModifier.NotEquals),
|
||||
Criterion.getModifierOption(CriterionModifier.GreaterThan),
|
||||
Criterion.getModifierOption(CriterionModifier.LessThan),
|
||||
];
|
||||
return ret;
|
||||
}
|
||||
case "age":
|
||||
return new MandatoryNumberCriterion(type, type);
|
||||
case "gender":
|
||||
return new GenderCriterion();
|
||||
case "ethnicity":
|
||||
|
||||
@@ -128,6 +128,8 @@ export class ListFilterModel {
|
||||
"duration",
|
||||
"framerate",
|
||||
"bitrate",
|
||||
"tag_count",
|
||||
"performer_count",
|
||||
"random",
|
||||
];
|
||||
this.displayModeOptions = [
|
||||
@@ -147,8 +149,10 @@ export class ListFilterModel {
|
||||
new HasMarkersCriterionOption(),
|
||||
new SceneIsMissingCriterionOption(),
|
||||
new TagsCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("tag_count"),
|
||||
new PerformerTagsCriterionOption(),
|
||||
new PerformersCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("performer_count"),
|
||||
new StudiosCriterionOption(),
|
||||
new MoviesCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("url"),
|
||||
@@ -163,6 +167,8 @@ export class ListFilterModel {
|
||||
"o_counter",
|
||||
"filesize",
|
||||
"file_mod_time",
|
||||
"tag_count",
|
||||
"performer_count",
|
||||
"random",
|
||||
];
|
||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall];
|
||||
@@ -175,8 +181,10 @@ export class ListFilterModel {
|
||||
new ResolutionCriterionOption(),
|
||||
new ImageIsMissingCriterionOption(),
|
||||
new TagsCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("tag_count"),
|
||||
new PerformerTagsCriterionOption(),
|
||||
new PerformersCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("performer_count"),
|
||||
new StudiosCriterionOption(),
|
||||
];
|
||||
break;
|
||||
@@ -187,6 +195,7 @@ export class ListFilterModel {
|
||||
"height",
|
||||
"birthdate",
|
||||
"scenes_count",
|
||||
"tag_count",
|
||||
"random",
|
||||
];
|
||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||
@@ -212,6 +221,10 @@ export class ListFilterModel {
|
||||
new PerformerIsMissingCriterionOption(),
|
||||
new TagsCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("url"),
|
||||
ListFilterModel.createCriterionOption("tag_count"),
|
||||
ListFilterModel.createCriterionOption("scene_count"),
|
||||
ListFilterModel.createCriterionOption("image_count"),
|
||||
ListFilterModel.createCriterionOption("gallery_count"),
|
||||
...numberCriteria
|
||||
.concat(stringCriteria)
|
||||
.map((c) => ListFilterModel.createCriterionOption(c)),
|
||||
@@ -247,6 +260,8 @@ export class ListFilterModel {
|
||||
"path",
|
||||
"file_mod_time",
|
||||
"images_count",
|
||||
"tag_count",
|
||||
"performer_count",
|
||||
"random",
|
||||
];
|
||||
this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List];
|
||||
@@ -258,8 +273,10 @@ export class ListFilterModel {
|
||||
new AverageResolutionCriterionOption(),
|
||||
new GalleryIsMissingCriterionOption(),
|
||||
new TagsCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("tag_count"),
|
||||
new PerformerTagsCriterionOption(),
|
||||
new PerformersCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("performer_count"),
|
||||
new StudiosCriterionOption(),
|
||||
ListFilterModel.createCriterionOption("url"),
|
||||
];
|
||||
@@ -563,6 +580,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "tag_count": {
|
||||
const tagCountCrit = criterion as NumberCriterion;
|
||||
result.tag_count = {
|
||||
value: tagCountCrit.value,
|
||||
modifier: tagCountCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performers": {
|
||||
const perfCrit = criterion as PerformersCriterion;
|
||||
result.performers = {
|
||||
@@ -571,6 +596,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performer_count": {
|
||||
const performerCountCrit = criterion as NumberCriterion;
|
||||
result.performer_count = {
|
||||
value: performerCountCrit.value,
|
||||
modifier: performerCountCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "studios": {
|
||||
const studCrit = criterion as StudiosCriterion;
|
||||
result.studios = {
|
||||
@@ -711,9 +744,42 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "tag_count": {
|
||||
const tagCountCrit = criterion as NumberCriterion;
|
||||
result.tag_count = {
|
||||
value: tagCountCrit.value,
|
||||
modifier: tagCountCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "scene_count": {
|
||||
const countCrit = criterion as NumberCriterion;
|
||||
result.scene_count = {
|
||||
value: countCrit.value,
|
||||
modifier: countCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "image_count": {
|
||||
const countCrit = criterion as NumberCriterion;
|
||||
result.image_count = {
|
||||
value: countCrit.value,
|
||||
modifier: countCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "gallery_count": {
|
||||
const countCrit = criterion as NumberCriterion;
|
||||
result.gallery_count = {
|
||||
value: countCrit.value,
|
||||
modifier: countCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
// no default
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -839,6 +905,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "tag_count": {
|
||||
const tagCountCrit = criterion as NumberCriterion;
|
||||
result.tag_count = {
|
||||
value: tagCountCrit.value,
|
||||
modifier: tagCountCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performerTags": {
|
||||
const performerTagsCrit = criterion as TagsCriterion;
|
||||
result.performer_tags = {
|
||||
@@ -855,6 +929,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performer_count": {
|
||||
const countCrit = criterion as NumberCriterion;
|
||||
result.performer_count = {
|
||||
value: countCrit.value,
|
||||
modifier: countCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "studios": {
|
||||
const studCrit = criterion as StudiosCriterion;
|
||||
result.studios = {
|
||||
@@ -1014,6 +1096,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "tag_count": {
|
||||
const tagCountCrit = criterion as NumberCriterion;
|
||||
result.tag_count = {
|
||||
value: tagCountCrit.value,
|
||||
modifier: tagCountCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performerTags": {
|
||||
const performerTagsCrit = criterion as TagsCriterion;
|
||||
result.performer_tags = {
|
||||
@@ -1030,6 +1120,14 @@ export class ListFilterModel {
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "performer_count": {
|
||||
const countCrit = criterion as NumberCriterion;
|
||||
result.performer_count = {
|
||||
value: countCrit.value,
|
||||
modifier: countCrit.modifier,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "studios": {
|
||||
const studCrit = criterion as StudiosCriterion;
|
||||
result.studios = {
|
||||
|
||||
Reference in New Issue
Block a user