From 985bc926a8c7c1580936b7cd1015cc228083149f Mon Sep 17 00:00:00 2001 From: Slick Daddy <129640104+slick-daddy@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:43:24 +0300 Subject: [PATCH] Add gallery o_counter filter option in UI list filters --- graphql/schema/types/filters.graphql | 2 ++ graphql/schema/types/gallery.graphql | 1 + internal/api/resolver_model_gallery.go | 12 +++++++++ pkg/models/gallery.go | 2 ++ pkg/models/mocks/ImageReaderWriter.go | 28 +++++++++++++++++++++ pkg/models/repository_image.go | 1 + pkg/sqlite/gallery_filter.go | 19 ++++++++++++++ pkg/sqlite/image.go | 13 ++++++++++ ui/v2.5/graphql/data/gallery-slim.graphql | 1 + ui/v2.5/graphql/data/gallery.graphql | 1 + ui/v2.5/src/models/list-filter/galleries.ts | 1 + 11 files changed, 81 insertions(+) diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index c7d880266..e56727574 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -576,6 +576,8 @@ input GalleryFilterType { performer_age: IntCriterionInput "Filter by number of images in this gallery" image_count: IntCriterionInput + "Filter by o-counter" + o_counter: IntCriterionInput "Filter by url" url: StringCriterionInput "Filter by date" diff --git a/graphql/schema/types/gallery.graphql b/graphql/schema/types/gallery.graphql index e28c3802b..1b41aeb4b 100644 --- a/graphql/schema/types/gallery.graphql +++ b/graphql/schema/types/gallery.graphql @@ -26,6 +26,7 @@ type Gallery { scenes: [Scene!]! studio: Studio image_count: Int! + o_counter: Int # Resolver tags: [Tag!]! performers: [Performer!]! diff --git a/internal/api/resolver_model_gallery.go b/internal/api/resolver_model_gallery.go index 773a831d8..f293c4daf 100644 --- a/internal/api/resolver_model_gallery.go +++ b/internal/api/resolver_model_gallery.go @@ -152,6 +152,18 @@ func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) ( return ret, nil } +func (r *galleryResolver) OCounter(ctx context.Context, obj *models.Gallery) (ret *int, err error) { + var count int + if err := r.withReadTxn(ctx, func(ctx context.Context) error { + count, err = r.repository.Image.OCountByGalleryID(ctx, obj.ID) + return err + }); err != nil { + return nil, err + } + + return &count, nil +} + func (r *galleryResolver) Chapters(ctx context.Context, obj *models.Gallery) (ret []*models.GalleryChapter, err error) { if err := r.withReadTxn(ctx, func(ctx context.Context) error { ret, err = r.repository.GalleryChapter.FindByGalleryID(ctx, obj.ID) diff --git a/pkg/models/gallery.go b/pkg/models/gallery.go index 3bf70b754..b260f552a 100644 --- a/pkg/models/gallery.go +++ b/pkg/models/gallery.go @@ -47,6 +47,8 @@ type GalleryFilterType struct { PerformerAge *IntCriterionInput `json:"performer_age"` // Filter by number of images in this gallery ImageCount *IntCriterionInput `json:"image_count"` + // Filter by o-counter + OCounter *IntCriterionInput `json:"o_counter"` // Filter by url URL *StringCriterionInput `json:"url"` // Filter by date diff --git a/pkg/models/mocks/ImageReaderWriter.go b/pkg/models/mocks/ImageReaderWriter.go index f2c9934be..1d2b70cf6 100644 --- a/pkg/models/mocks/ImageReaderWriter.go +++ b/pkg/models/mocks/ImageReaderWriter.go @@ -619,6 +619,34 @@ func (_m *ImageReaderWriter) OCount(ctx context.Context) (int, error) { return r0, r1 } +// OCountByGalleryID provides a mock function with given fields: ctx, galleryID +func (_m *ImageReaderWriter) OCountByGalleryID(ctx context.Context, galleryID int) (int, error) { + ret := _m.Called(ctx, galleryID) + + if len(ret) == 0 { + panic("no return value specified for OCountByGalleryID") + } + + var r0 int + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int) (int, error)); ok { + return rf(ctx, galleryID) + } + if rf, ok := ret.Get(0).(func(context.Context, int) int); ok { + r0 = rf(ctx, galleryID) + } else { + r0 = ret.Get(0).(int) + } + + if rf, ok := ret.Get(1).(func(context.Context, int) error); ok { + r1 = rf(ctx, galleryID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // OCountByPerformerID provides a mock function with given fields: ctx, performerID func (_m *ImageReaderWriter) OCountByPerformerID(ctx context.Context, performerID int) (int, error) { ret := _m.Called(ctx, performerID) diff --git a/pkg/models/repository_image.go b/pkg/models/repository_image.go index 99dab3479..566c393c8 100644 --- a/pkg/models/repository_image.go +++ b/pkg/models/repository_image.go @@ -37,6 +37,7 @@ type ImageCounter interface { CountByFileID(ctx context.Context, fileID FileID) (int, error) CountByGalleryID(ctx context.Context, galleryID int) (int, error) OCount(ctx context.Context) (int, error) + OCountByGalleryID(ctx context.Context, galleryID int) (int, error) OCountByPerformerID(ctx context.Context, performerID int) (int, error) OCountByStudioID(ctx context.Context, studioID int) (int, error) } diff --git a/pkg/sqlite/gallery_filter.go b/pkg/sqlite/gallery_filter.go index c70af1308..717f2c051 100644 --- a/pkg/sqlite/gallery_filter.go +++ b/pkg/sqlite/gallery_filter.go @@ -100,6 +100,7 @@ func (qb *galleryFilterHandler) criterionHandler() criterionHandler { qb.performerTagsCriterionHandler(filter.PerformerTags), qb.averageResolutionCriterionHandler(filter.AverageResolution), qb.imageCountCriterionHandler(filter.ImageCount), + qb.galleryOCounterCriterionHandler(filter.OCounter), qb.performerFavoriteCriterionHandler(filter.PerformerFavorite), qb.performerAgeCriterionHandler(filter.PerformerAge), &dateCriterionHandler{filter.Date, "galleries.date", nil}, @@ -187,6 +188,24 @@ func (qb *galleryFilterHandler) criterionHandler() criterionHandler { } } +var selectGalleryOCountSQL = `SELECT COALESCE(SUM(images.o_counter), 0) +FROM galleries_images +LEFT JOIN images ON images.id = galleries_images.image_id +WHERE galleries_images.gallery_id = galleries.id` + +func (qb *galleryFilterHandler) galleryOCounterCriterionHandler(count *models.IntCriterionInput) criterionHandlerFunc { + return func(ctx context.Context, f *filterBuilder) { + if count == nil { + return + } + + lhs := "(" + selectGalleryOCountSQL + ")" + clause, args := getIntCriterionWhereClause(lhs, *count) + + f.addWhere(clause, args...) + } +} + func (qb *galleryFilterHandler) urlsCriterionHandler(url *models.StringCriterionInput) criterionHandlerFunc { h := stringListCriterionHandlerBuilder{ primaryTable: galleryTable, diff --git a/pkg/sqlite/image.go b/pkg/sqlite/image.go index 4d9ebad1b..af3ea6a81 100644 --- a/pkg/sqlite/image.go +++ b/pkg/sqlite/image.go @@ -687,6 +687,19 @@ func (qb *ImageStore) CountByGalleryID(ctx context.Context, galleryID int) (int, return count(ctx, q) } +func (qb *ImageStore) OCountByGalleryID(ctx context.Context, galleryID int) (int, error) { + table := qb.table() + joinTable := galleriesImagesJoinTable + q := dialect.Select(goqu.COALESCE(goqu.SUM("o_counter"), 0)).From(table).InnerJoin(joinTable, goqu.On(table.Col(idColumn).Eq(joinTable.Col(imageIDColumn)))).Where(joinTable.Col(galleryIDColumn).Eq(galleryID)) + + var ret int + if err := querySimple(ctx, q, &ret); err != nil { + return 0, err + } + + return ret, nil +} + func (qb *ImageStore) OCountByPerformerID(ctx context.Context, performerID int) (int, error) { table := qb.table() joinTable := performersImagesJoinTable diff --git a/ui/v2.5/graphql/data/gallery-slim.graphql b/ui/v2.5/graphql/data/gallery-slim.graphql index 633071e3f..436b91e66 100644 --- a/ui/v2.5/graphql/data/gallery-slim.graphql +++ b/ui/v2.5/graphql/data/gallery-slim.graphql @@ -8,6 +8,7 @@ fragment SlimGalleryData on Gallery { photographer rating100 organized + o_counter files { ...GalleryFileData } diff --git a/ui/v2.5/graphql/data/gallery.graphql b/ui/v2.5/graphql/data/gallery.graphql index 349a52ad7..222319394 100644 --- a/ui/v2.5/graphql/data/gallery.graphql +++ b/ui/v2.5/graphql/data/gallery.graphql @@ -10,6 +10,7 @@ fragment GalleryData on Gallery { photographer rating100 organized + o_counter paths { cover diff --git a/ui/v2.5/src/models/list-filter/galleries.ts b/ui/v2.5/src/models/list-filter/galleries.ts index ed0b2c155..6126cd2ab 100644 --- a/ui/v2.5/src/models/list-filter/galleries.ts +++ b/ui/v2.5/src/models/list-filter/galleries.ts @@ -69,6 +69,7 @@ const criterionOptions = [ PerformerAgeCriterionOption, PerformerFavoriteCriterionOption, createMandatoryNumberCriterionOption("image_count"), + createMandatoryNumberCriterionOption("o_counter", "o_count"), // StudioTagsCriterionOption, ScenesCriterionOption, StudiosCriterionOption,