FR: Add Missing is-missing Filter Options Across all Object Types (#6565)

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
Gykes 2026-02-25 21:36:54 -08:00 committed by GitHub
parent ed58d18334
commit b77abd64e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 142 additions and 6 deletions

View file

@ -308,7 +308,16 @@ func (qb *galleryFilterHandler) missingCriterionHandler(isMissing *string) crite
case "tags":
galleryRepository.tags.join(f, "tags_join", "galleries.id")
f.addWhere("tags_join.gallery_id IS NULL")
case "cover":
f.addLeftJoin("galleries_images", "cover_join", "cover_join.gallery_id = galleries.id AND cover_join.cover = 1")
f.addWhere("cover_join.image_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"title", "code", "rating", "details", "photographer",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(galleries." + *isMissing + " IS NULL OR TRIM(galleries." + *isMissing + ") = '')")
}
}

View file

@ -119,7 +119,25 @@ func (qb *groupFilterHandler) missingCriterionHandler(isMissing *string) criteri
case "scenes":
f.addLeftJoin("groups_scenes", "", "groups_scenes.group_id = groups.id")
f.addWhere("groups_scenes.scene_id IS NULL")
case "url":
groupsURLsTableMgr.join(f, "", "groups.id")
f.addWhere("group_urls.url IS NULL")
case "studio":
f.addWhere("groups.studio_id IS NULL")
case "performers":
f.addLeftJoin("groups_scenes", "gs_perf", "groups.id = gs_perf.group_id")
f.addLeftJoin("performers_scenes", "ps_perf", "gs_perf.scene_id = ps_perf.scene_id")
f.addWhere("ps_perf.performer_id IS NULL")
case "tags":
groupRepository.tags.join(f, "tags_join", "groups.id")
f.addWhere("tags_join.group_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"aliases", "description", "director", "date", "rating",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(groups." + *isMissing + " IS NULL OR TRIM(groups." + *isMissing + ") = '')")
}
}

View file

@ -171,6 +171,9 @@ func (qb *imageFilterHandler) missingCriterionHandler(isMissing *string) criteri
return func(ctx context.Context, f *filterBuilder) {
if isMissing != nil && *isMissing != "" {
switch *isMissing {
case "url":
imagesURLsTableMgr.join(f, "", "images.id")
f.addWhere("image_urls.url IS NULL")
case "studio":
f.addWhere("images.studio_id IS NULL")
case "performers":
@ -183,6 +186,12 @@ func (qb *imageFilterHandler) missingCriterionHandler(isMissing *string) criteri
imageRepository.tags.join(f, "tags_join", "images.id")
f.addWhere("tags_join.image_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"title", "details", "photographer", "date", "code", "rating",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(images." + *isMissing + " IS NULL OR TRIM(images." + *isMissing + ") = '')")
}
}

View file

@ -316,7 +316,19 @@ func (qb *performerFilterHandler) performerIsMissingCriterionHandler(isMissing *
case "aliases":
performersAliasesTableMgr.join(f, "", "performers.id")
f.addWhere("performer_aliases.alias IS NULL")
case "tags":
f.addLeftJoin(performersTagsTable, "tags_join", "tags_join.performer_id = performers.id")
f.addWhere("tags_join.performer_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"disambiguation", "gender", "birthdate", "death_date",
"ethnicity", "country", "hair_color", "eye_color", "height", "weight",
"measurements", "fake_tits", "penis_length", "circumcised",
"career_start", "career_end", "tattoos", "piercings", "details", "rating",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(performers." + *isMissing + " IS NULL OR TRIM(performers." + *isMissing + ") = '')")
}
}

View file

@ -426,6 +426,12 @@ func (qb *sceneFilterHandler) isMissingCriterionHandler(isMissing *string) crite
case "cover":
f.addWhere("scenes.cover_blob IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"title", "code", "details", "director", "rating",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(scenes." + *isMissing + " IS NULL OR TRIM(scenes." + *isMissing + ") = '')")
}
}

View file

@ -71,6 +71,16 @@ func (o sortOptions) validateSort(sort string) error {
return fmt.Errorf("invalid sort: %s", sort)
}
func validateIsMissing(isMissing string, allowed []string) error {
for _, v := range allowed {
if v == isMissing {
return nil
}
}
return fmt.Errorf("invalid is_missing field: %s", isMissing)
}
func getSortDirection(direction string) string {
if direction != "ASC" && direction != "DESC" {
return "ASC"

View file

@ -150,7 +150,19 @@ func (qb *studioFilterHandler) isMissingCriterionHandler(isMissing *string) crit
case "stash_id":
studioRepository.stashIDs.join(f, "studio_stash_ids", "studios.id")
f.addWhere("studio_stash_ids.studio_id IS NULL")
case "aliases":
studiosAliasesTableMgr.join(f, "", "studios.id")
f.addWhere("studio_aliases.alias IS NULL")
case "tags":
f.addLeftJoin(studiosTagsTable, "tags_join", "tags_join.studio_id = studios.id")
f.addWhere("tags_join.studio_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"details", "rating",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(studios." + *isMissing + " IS NULL OR TRIM(studios." + *isMissing + ") = '')")
}
}

View file

@ -198,7 +198,19 @@ func (qb *tagFilterHandler) isMissingCriterionHandler(isMissing *string) criteri
switch *isMissing {
case "image":
f.addWhere("tags.image_blob IS NULL")
case "aliases":
tagRepository.aliases.join(f, "", "tags.id")
f.addWhere("tag_aliases.alias IS NULL")
case "stash_id":
tagRepository.stashIDs.join(f, "tag_stash_ids", "tags.id")
f.addWhere("tag_stash_ids.tag_id IS NULL")
default:
if err := validateIsMissing(*isMissing, []string{
"description",
}); err != nil {
f.setError(err)
return
}
f.addWhere("(tags." + *isMissing + " IS NULL OR TRIM(tags." + *isMissing + ") = '')")
}
}

View file

@ -26,10 +26,13 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOption(
"is_missing",
[
"title",
"cover",
"code",
"details",
"director",
"url",
"date",
"rating",
"cover",
"galleries",
"studio",
"group",
@ -42,7 +45,19 @@ export const SceneIsMissingCriterionOption = new IsMissingCriterionOption(
export const ImageIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing",
"is_missing",
["title", "galleries", "studio", "performers", "tags"]
[
"title",
"details",
"photographer",
"url",
"date",
"code",
"rating",
"galleries",
"studio",
"performers",
"tags",
]
);
export const PerformerIsMissingCriterionOption = new IsMissingCriterionOption(
@ -58,14 +73,21 @@ export const PerformerIsMissingCriterionOption = new IsMissingCriterionOption(
"weight",
"measurements",
"fake_tits",
"penis_length",
"circumcised",
"career_start",
"career_end",
"tattoos",
"piercings",
"aliases",
"gender",
"birthdate",
"death_date",
"disambiguation",
"tags",
"image",
"details",
"rating",
"stash_id",
]
);
@ -73,23 +95,49 @@ export const PerformerIsMissingCriterionOption = new IsMissingCriterionOption(
export const GalleryIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing",
"is_missing",
["title", "details", "url", "date", "studio", "performers", "tags", "scenes"]
[
"title",
"code",
"details",
"photographer",
"url",
"date",
"rating",
"cover",
"studio",
"performers",
"tags",
"scenes",
]
);
export const TagIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing",
"is_missing",
["image"]
["image", "aliases", "description", "stash_id"]
);
export const StudioIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing",
"is_missing",
["image", "stash_id", "details"]
["image", "stash_id", "details", "url", "aliases", "tags", "rating"]
);
export const GroupIsMissingCriterionOption = new IsMissingCriterionOption(
"isMissing",
"is_missing",
["front_image", "back_image", "scenes"]
[
"aliases",
"description",
"director",
"date",
"url",
"rating",
"studio",
"performers",
"tags",
"front_image",
"back_image",
"scenes",
]
);