Support filtering by StashID endpoint (#3005)

* Add endpoint to stash_id filter in UI

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
stg-annon
2022-11-16 18:08:15 -05:00
committed by GitHub
parent ca9c8e0a34
commit 3660bf2d1a
22 changed files with 481 additions and 7 deletions

View File

@@ -39,6 +39,14 @@ input PHashDuplicationCriterionInput {
distance: Int distance: Int
} }
input StashIDCriterionInput {
"""If present, this value is treated as a predicate.
That is, it will filter based on stash_ids with the matching endpoint"""
endpoint: String
stash_id: String
modifier: CriterionModifier!
}
input PerformerFilterType { input PerformerFilterType {
AND: PerformerFilterType AND: PerformerFilterType
OR: PerformerFilterType OR: PerformerFilterType
@@ -90,7 +98,9 @@ input PerformerFilterType {
"""Filter by gallery count""" """Filter by gallery count"""
gallery_count: IntCriterionInput gallery_count: IntCriterionInput
"""Filter by StashID""" """Filter by StashID"""
stash_id: StringCriterionInput stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter by rating""" """Filter by rating"""
rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100") rating: IntCriterionInput @deprecated(reason: "Use 1-100 range with rating100")
# rating expressed as 1-100 # rating expressed as 1-100
@@ -196,7 +206,9 @@ input SceneFilterType {
"""Filter by performer count""" """Filter by performer count"""
performer_count: IntCriterionInput performer_count: IntCriterionInput
"""Filter by StashID""" """Filter by StashID"""
stash_id: StringCriterionInput stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter by url""" """Filter by url"""
url: StringCriterionInput url: StringCriterionInput
"""Filter by interactive""" """Filter by interactive"""
@@ -251,7 +263,9 @@ input StudioFilterType {
"""Filter to only include studios with this parent studio""" """Filter to only include studios with this parent studio"""
parents: MultiCriterionInput parents: MultiCriterionInput
"""Filter by StashID""" """Filter by StashID"""
stash_id: StringCriterionInput stash_id: StringCriterionInput @deprecated(reason: "Use stash_id_endpoint instead")
"""Filter by StashID"""
stash_id_endpoint: StashIDCriterionInput
"""Filter to only include studios missing this property""" """Filter to only include studios missing this property"""
is_missing: String is_missing: String
"""Filter by rating""" """Filter by rating"""

View File

@@ -111,6 +111,8 @@ type PerformerFilterType struct {
GalleryCount *IntCriterionInput `json:"gallery_count"` GalleryCount *IntCriterionInput `json:"gallery_count"`
// Filter by StashID // Filter by StashID
StashID *StringCriterionInput `json:"stash_id"` StashID *StringCriterionInput `json:"stash_id"`
// Filter by StashID Endpoint
StashIDEndpoint *StashIDCriterionInput `json:"stash_id_endpoint"`
// Filter by rating expressed as 1-5 // Filter by rating expressed as 1-5
Rating *IntCriterionInput `json:"rating"` Rating *IntCriterionInput `json:"rating"`
// Filter by rating expressed as 1-100 // Filter by rating expressed as 1-100

View File

@@ -69,6 +69,8 @@ type SceneFilterType struct {
PerformerCount *IntCriterionInput `json:"performer_count"` PerformerCount *IntCriterionInput `json:"performer_count"`
// Filter by StashID // Filter by StashID
StashID *StringCriterionInput `json:"stash_id"` StashID *StringCriterionInput `json:"stash_id"`
// Filter by StashID Endpoint
StashIDEndpoint *StashIDCriterionInput `json:"stash_id_endpoint"`
// Filter by url // Filter by url
URL *StringCriterionInput `json:"url"` URL *StringCriterionInput `json:"url"`
// Filter by interactive // Filter by interactive

View File

@@ -20,3 +20,11 @@ func (u *UpdateStashIDs) AddUnique(v StashID) {
u.StashIDs = append(u.StashIDs, v) u.StashIDs = append(u.StashIDs, v)
} }
type StashIDCriterionInput struct {
// If present, this value is treated as a predicate.
// That is, it will filter based on stash_ids with the matching endpoint
Endpoint *string `json:"endpoint"`
StashID *string `json:"stash_id"`
Modifier CriterionModifier `json:"modifier"`
}

View File

@@ -12,6 +12,8 @@ type StudioFilterType struct {
Parents *MultiCriterionInput `json:"parents"` Parents *MultiCriterionInput `json:"parents"`
// Filter by StashID // Filter by StashID
StashID *StringCriterionInput `json:"stash_id"` StashID *StringCriterionInput `json:"stash_id"`
// Filter by StashID Endpoint
StashIDEndpoint *StashIDCriterionInput `json:"stash_id_endpoint"`
// Filter to only include studios missing this property // Filter to only include studios missing this property
IsMissing *string `json:"is_missing"` IsMissing *string `json:"is_missing"`
// Filter by rating expressed as 1-5 // Filter by rating expressed as 1-5

View File

@@ -942,3 +942,39 @@ func (m *joinedHierarchicalMultiCriterionHandlerBuilder) handler(criterion *mode
} }
} }
} }
type stashIDCriterionHandler struct {
c *models.StashIDCriterionInput
stashIDRepository *stashIDRepository
stashIDTableAs string
parentIDCol string
}
func (h *stashIDCriterionHandler) handle(ctx context.Context, f *filterBuilder) {
if h.c == nil {
return
}
stashIDRepo := h.stashIDRepository
t := stashIDRepo.tableName
if h.stashIDTableAs != "" {
t = h.stashIDTableAs
}
joinClause := fmt.Sprintf("%s.%s = %s", t, stashIDRepo.idColumn, h.parentIDCol)
if h.c.Endpoint != nil && *h.c.Endpoint != "" {
joinClause += fmt.Sprintf(" AND %s.endpoint = '%s'", t, *h.c.Endpoint)
}
f.addLeftJoin(stashIDRepo.tableName, h.stashIDTableAs, joinClause)
v := ""
if h.c.StashID != nil {
v = *h.c.StashID
}
stringCriterionHandler(&models.StringCriterionInput{
Value: v,
Modifier: h.c.Modifier,
}, t+".stash_id")(ctx, f)
}

View File

@@ -533,6 +533,12 @@ func (qb *PerformerStore) makeFilter(ctx context.Context, filter *models.Perform
stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(ctx, f) stringCriterionHandler(filter.StashID, "performer_stash_ids.stash_id")(ctx, f)
} }
})) }))
query.handleCriterion(ctx, &stashIDCriterionHandler{
c: filter.StashIDEndpoint,
stashIDRepository: qb.stashIDRepository(),
stashIDTableAs: "performer_stash_ids",
parentIDCol: "performers.id",
})
// TODO - need better handling of aliases // TODO - need better handling of aliases
query.handleCriterion(ctx, stringCriterionHandler(filter.Aliases, tableName+".aliases")) query.handleCriterion(ctx, stringCriterionHandler(filter.Aliases, tableName+".aliases"))

View File

@@ -585,6 +585,100 @@ func TestPerformerQueryIgnoreAutoTag(t *testing.T) {
}) })
} }
func TestPerformerQuery(t *testing.T) {
var (
endpoint = performerStashID(performerIdxWithGallery).Endpoint
stashID = performerStashID(performerIdxWithGallery).StashID
)
tests := []struct {
name string
findFilter *models.FindFilterType
filter *models.PerformerFilterType
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"stash id with endpoint",
nil,
&models.PerformerFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierEquals,
},
},
[]int{performerIdxWithGallery},
nil,
false,
},
{
"exclude stash id with endpoint",
nil,
&models.PerformerFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierNotEquals,
},
},
nil,
[]int{performerIdxWithGallery},
false,
},
{
"null stash id with endpoint",
nil,
&models.PerformerFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierIsNull,
},
},
nil,
[]int{performerIdxWithGallery},
false,
},
{
"not null stash id with endpoint",
nil,
&models.PerformerFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierNotNull,
},
},
[]int{performerIdxWithGallery},
nil,
false,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
performers, _, err := db.Performer.Query(ctx, tt.filter, tt.findFilter)
if (err != nil) != tt.wantErr {
t.Errorf("PerformerStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
ids := performersToIDs(performers)
include := indexesToIDs(performerIDs, tt.includeIdxs)
exclude := indexesToIDs(performerIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(ids, i)
}
for _, e := range exclude {
assert.NotContains(ids, e)
}
})
}
}
func TestPerformerQueryForAutoTag(t *testing.T) { func TestPerformerQueryForAutoTag(t *testing.T) {
withTxn(func(ctx context.Context) error { withTxn(func(ctx context.Context) error {
tqb := db.Performer tqb := db.Performer

View File

@@ -864,6 +864,12 @@ func (qb *SceneStore) makeFilter(ctx context.Context, sceneFilter *models.SceneF
stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id")(ctx, f) stringCriterionHandler(sceneFilter.StashID, "scene_stash_ids.stash_id")(ctx, f)
} }
})) }))
query.handleCriterion(ctx, &stashIDCriterionHandler{
c: sceneFilter.StashIDEndpoint,
stashIDRepository: qb.stashIDRepository(),
stashIDTableAs: "scene_stash_ids",
parentIDCol: "scenes.id",
})
query.handleCriterion(ctx, boolCriterionHandler(sceneFilter.Interactive, "video_files.interactive", qb.addVideoFilesTable)) query.handleCriterion(ctx, boolCriterionHandler(sceneFilter.Interactive, "video_files.interactive", qb.addVideoFilesTable))
query.handleCriterion(ctx, intCriterionHandler(sceneFilter.InteractiveSpeed, "video_files.interactive_speed", qb.addVideoFilesTable)) query.handleCriterion(ctx, intCriterionHandler(sceneFilter.InteractiveSpeed, "video_files.interactive_speed", qb.addVideoFilesTable))

View File

@@ -2091,6 +2091,104 @@ func sceneQueryQ(ctx context.Context, t *testing.T, sqb models.SceneReader, q st
assert.Len(t, scenes, totalScenes) assert.Len(t, scenes, totalScenes)
} }
func TestSceneQuery(t *testing.T) {
var (
endpoint = sceneStashID(sceneIdxWithGallery).Endpoint
stashID = sceneStashID(sceneIdxWithGallery).StashID
)
tests := []struct {
name string
findFilter *models.FindFilterType
filter *models.SceneFilterType
includeIdxs []int
excludeIdxs []int
wantErr bool
}{
{
"stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierEquals,
},
},
[]int{sceneIdxWithGallery},
nil,
false,
},
{
"exclude stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
StashID: &stashID,
Modifier: models.CriterionModifierNotEquals,
},
},
nil,
[]int{sceneIdxWithGallery},
false,
},
{
"null stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierIsNull,
},
},
nil,
[]int{sceneIdxWithGallery},
false,
},
{
"not null stash id with endpoint",
nil,
&models.SceneFilterType{
StashIDEndpoint: &models.StashIDCriterionInput{
Endpoint: &endpoint,
Modifier: models.CriterionModifierNotNull,
},
},
[]int{sceneIdxWithGallery},
nil,
false,
},
}
for _, tt := range tests {
runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
assert := assert.New(t)
results, err := db.Scene.Query(ctx, models.SceneQueryOptions{
SceneFilter: tt.filter,
QueryOptions: models.QueryOptions{
FindFilter: tt.findFilter,
},
})
if (err != nil) != tt.wantErr {
t.Errorf("PerformerStore.Query() error = %v, wantErr %v", err, tt.wantErr)
return
}
include := indexesToIDs(performerIDs, tt.includeIdxs)
exclude := indexesToIDs(performerIDs, tt.excludeIdxs)
for _, i := range include {
assert.Contains(results.IDs, i)
}
for _, e := range exclude {
assert.NotContains(results.IDs, e)
}
})
}
}
func TestSceneQueryPath(t *testing.T) { func TestSceneQueryPath(t *testing.T) {
const ( const (
sceneIdx = 1 sceneIdx = 1

View File

@@ -245,6 +245,12 @@ func (qb *studioQueryBuilder) makeFilter(ctx context.Context, studioFilter *mode
stringCriterionHandler(studioFilter.StashID, "studio_stash_ids.stash_id")(ctx, f) stringCriterionHandler(studioFilter.StashID, "studio_stash_ids.stash_id")(ctx, f)
} }
})) }))
query.handleCriterion(ctx, &stashIDCriterionHandler{
c: studioFilter.StashIDEndpoint,
stashIDRepository: qb.stashIDRepository(),
stashIDTableAs: "studio_stash_ids",
parentIDCol: "studios.id",
})
query.handleCriterion(ctx, studioIsMissingCriterionHandler(qb, studioFilter.IsMissing)) query.handleCriterion(ctx, studioIsMissingCriterionHandler(qb, studioFilter.IsMissing))
query.handleCriterion(ctx, studioSceneCountCriterionHandler(qb, studioFilter.SceneCount)) query.handleCriterion(ctx, studioSceneCountCriterionHandler(qb, studioFilter.SceneCount))

View File

@@ -22,6 +22,7 @@ import { FormattedMessage, useIntl } from "react-intl";
import { import {
criterionIsHierarchicalLabelValue, criterionIsHierarchicalLabelValue,
criterionIsNumberValue, criterionIsNumberValue,
criterionIsStashIDValue,
criterionIsDateValue, criterionIsDateValue,
criterionIsTimestampValue, criterionIsTimestampValue,
CriterionType, CriterionType,
@@ -36,6 +37,8 @@ import { DateFilter } from "./Filters/DateFilter";
import { TimestampFilter } from "./Filters/TimestampFilter"; import { TimestampFilter } from "./Filters/TimestampFilter";
import { CountryCriterion } from "src/models/list-filter/criteria/country"; import { CountryCriterion } from "src/models/list-filter/criteria/country";
import { CountrySelect } from "../Shared"; import { CountrySelect } from "../Shared";
import { StashIDCriterion } from "src/models/list-filter/criteria/stash-ids";
import { StashIDFilter } from "./Filters/StashIDFilter";
import { ConfigurationContext } from "src/hooks/Config"; import { ConfigurationContext } from "src/hooks/Config";
import { RatingCriterion } from "../../models/list-filter/criteria/rating"; import { RatingCriterion } from "../../models/list-filter/criteria/rating";
import { RatingFilter } from "./Filters/RatingFilter"; import { RatingFilter } from "./Filters/RatingFilter";
@@ -134,6 +137,16 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
} }
function renderSelect() { function renderSelect() {
// always show stashID filter
if (criterion instanceof StashIDCriterion) {
return (
<StashIDFilter
criterion={criterion}
onValueChanged={onValueChanged}
/>
);
}
// Hide the value select if the modifier is "IsNull" or "NotNull" // Hide the value select if the modifier is "IsNull" or "NotNull"
if ( if (
criterion.modifier === CriterionModifier.IsNull || criterion.modifier === CriterionModifier.IsNull ||
@@ -162,6 +175,7 @@ export const AddFilterDialog: React.FC<IAddFilterProps> = ({
options && options &&
!criterionIsHierarchicalLabelValue(criterion.value) && !criterionIsHierarchicalLabelValue(criterion.value) &&
!criterionIsNumberValue(criterion.value) && !criterionIsNumberValue(criterion.value) &&
!criterionIsStashIDValue(criterion.value) &&
!criterionIsDateValue(criterion.value) && !criterionIsDateValue(criterion.value) &&
!criterionIsTimestampValue(criterion.value) && !criterionIsTimestampValue(criterion.value) &&
!Array.isArray(criterion.value) !Array.isArray(criterion.value)

View File

@@ -0,0 +1,56 @@
import React from "react";
import { Form } from "react-bootstrap";
import { useIntl } from "react-intl";
import { IStashIDValue } from "../../../models/list-filter/types";
import { Criterion } from "../../../models/list-filter/criteria/criterion";
import { CriterionModifier } from "src/core/generated-graphql";
interface IStashIDFilterProps {
criterion: Criterion<IStashIDValue>;
onValueChanged: (value: IStashIDValue) => void;
}
export const StashIDFilter: React.FC<IStashIDFilterProps> = ({
criterion,
onValueChanged,
}) => {
const intl = useIntl();
function onEndpointChanged(event: React.ChangeEvent<HTMLInputElement>) {
onValueChanged({
endpoint: event.target.value,
stashID: criterion.value.stashID,
});
}
function onStashIDChanged(event: React.ChangeEvent<HTMLInputElement>) {
onValueChanged({
stashID: event.target.value,
endpoint: criterion.value.endpoint,
});
}
return (
<div>
<Form.Group>
<Form.Control
className="btn-secondary"
onBlur={onEndpointChanged}
defaultValue={criterion.value ? criterion.value.endpoint : ""}
placeholder={intl.formatMessage({ id: "stash_id_endpoint" })}
/>
</Form.Group>
{criterion.modifier !== CriterionModifier.IsNull &&
criterion.modifier !== CriterionModifier.NotNull && (
<Form.Group>
<Form.Control
className="btn-secondary"
onBlur={onStashIDChanged}
defaultValue={criterion.value ? criterion.value.stashID : ""}
placeholder={intl.formatMessage({ id: "stash_id" })}
/>
</Form.Group>
)}
</div>
);
};

View File

@@ -1,4 +1,5 @@
### ✨ New Features ### ✨ New Features
* Added support for filtering stash ids by endpoint. ([#3005](https://github.com/stashapp/stash/pull/3005))
* Added custom javascript option. ([#3132](https://github.com/stashapp/stash/pull/3132)) * Added custom javascript option. ([#3132](https://github.com/stashapp/stash/pull/3132))
* Added ability to select rating system in the Interface settings, allowing 5 stars with full-, half- or quarter-stars, or numeric score out of 10 with one decimal point. ([#2830](https://github.com/stashapp/stash/pull/2830)) * Added ability to select rating system in the Interface settings, allowing 5 stars with full-, half- or quarter-stars, or numeric score out of 10 with one decimal point. ([#2830](https://github.com/stashapp/stash/pull/2830))
* Added filter criteria for Birthdate, Death Date, Date, Created At and Updated At fields. ([#2834](https://github.com/stashapp/stash/pull/2834)) * Added filter criteria for Birthdate, Death Date, Date, Created At and Updated At fields. ([#2834](https://github.com/stashapp/stash/pull/2834))

View File

@@ -1060,6 +1060,7 @@
"submit_update": "Already exists in {endpoint_name}" "submit_update": "Already exists in {endpoint_name}"
}, },
"statistics": "Statistics", "statistics": "Statistics",
"stash_id_endpoint": "Stash ID Endpoint",
"stats": { "stats": {
"image_size": "Images size", "image_size": "Images size",
"scenes_duration": "Scenes duration", "scenes_duration": "Scenes duration",

View File

@@ -19,6 +19,7 @@ import {
ILabeledValue, ILabeledValue,
INumberValue, INumberValue,
IOptionType, IOptionType,
IStashIDValue,
IDateValue, IDateValue,
ITimestampValue, ITimestampValue,
} from "../types"; } from "../types";
@@ -29,6 +30,7 @@ export type CriterionValue =
| ILabeledId[] | ILabeledId[]
| IHierarchicalLabelValue | IHierarchicalLabelValue
| INumberValue | INumberValue
| IStashIDValue
| IDateValue | IDateValue
| ITimestampValue; | ITimestampValue;

View File

@@ -50,6 +50,7 @@ import { DuplicatedCriterion, PhashCriterionOption } from "./phash";
import { CaptionCriterion } from "./captions"; import { CaptionCriterion } from "./captions";
import { RatingCriterion } from "./rating"; import { RatingCriterion } from "./rating";
import { CountryCriterion } from "./country"; import { CountryCriterion } from "./country";
import { StashIDCriterion } from "./stash-ids";
import * as GQL from "src/core/generated-graphql"; import * as GQL from "src/core/generated-graphql";
import { IUIConfig } from "src/core/config"; import { IUIConfig } from "src/core/config";
import { defaultRatingSystemOptions } from "src/utils/rating"; import { defaultRatingSystemOptions } from "src/utils/rating";
@@ -169,6 +170,10 @@ export function makeCriteria(
return new NumberCriterion( return new NumberCriterion(
new NumberCriterionOption("height", "height_cm", type) new NumberCriterionOption("height", "height_cm", type)
); );
// stash_id is deprecated
case "stash_id":
case "stash_id_endpoint":
return new StashIDCriterion();
case "ethnicity": case "ethnicity":
case "hair_color": case "hair_color":
case "eye_color": case "eye_color":
@@ -179,7 +184,6 @@ export function makeCriteria(
case "piercings": case "piercings":
case "aliases": case "aliases":
case "url": case "url":
case "stash_id":
case "details": case "details":
case "title": case "title":
case "director": case "director":

View File

@@ -0,0 +1,106 @@
/* eslint @typescript-eslint/no-unused-vars: ["error", { "argsIgnorePattern": "^_" }] */
import { IntlShape } from "react-intl";
import {
CriterionModifier,
StashIdCriterionInput,
} from "src/core/generated-graphql";
import { IStashIDValue } from "../types";
import { Criterion, CriterionOption } from "./criterion";
export const StashIDCriterionOption = new CriterionOption({
messageID: "stash_id",
type: "stash_id_endpoint",
parameterName: "stash_id_endpoint",
modifierOptions: [
CriterionModifier.Equals,
CriterionModifier.NotEquals,
CriterionModifier.IsNull,
CriterionModifier.NotNull,
],
});
export class StashIDCriterion extends Criterion<IStashIDValue> {
constructor() {
super(StashIDCriterionOption, {
endpoint: "",
stashID: "",
});
}
public get value(): IStashIDValue {
return this._value;
}
public set value(newValue: string | IStashIDValue) {
// backwards compatibility - if this.value is a string, use that as stash_id
if (typeof newValue !== "object") {
this._value = {
endpoint: "",
stashID: newValue,
};
} else {
this._value = newValue;
}
}
protected toCriterionInput(): StashIdCriterionInput {
return {
endpoint: this.value.endpoint,
stash_id: this.value.stashID,
modifier: this.modifier,
};
}
public getLabel(intl: IntlShape): string {
const modifierString = Criterion.getModifierLabel(intl, this.modifier);
let valueString = "";
if (
this.modifier !== CriterionModifier.IsNull &&
this.modifier !== CriterionModifier.NotNull
) {
valueString = this.getLabelValue(intl);
} else if (this.value.endpoint) {
valueString = "(" + this.value.endpoint + ")";
}
return intl.formatMessage(
{ id: "criterion_modifier.format_string" },
{
criterion: intl.formatMessage({ id: this.criterionOption.messageID }),
modifierString,
valueString,
}
);
}
public getLabelValue(_intl: IntlShape) {
let ret = this.value.stashID;
if (this.value.endpoint) {
ret += " (" + this.value.endpoint + ")";
}
return ret;
}
public toJSON() {
let encodedCriterion;
if (
(this.modifier === CriterionModifier.IsNull ||
this.modifier === CriterionModifier.NotNull) &&
!this.value.endpoint
) {
encodedCriterion = {
type: this.criterionOption.type,
modifier: this.modifier,
};
} else {
encodedCriterion = {
type: this.criterionOption.type,
value: this.value,
modifier: this.modifier,
};
}
return JSON.stringify(encodedCriterion);
}
}

View File

@@ -11,6 +11,7 @@ import {
import { FavoriteCriterionOption } from "./criteria/favorite"; import { FavoriteCriterionOption } from "./criteria/favorite";
import { GenderCriterionOption } from "./criteria/gender"; import { GenderCriterionOption } from "./criteria/gender";
import { PerformerIsMissingCriterionOption } from "./criteria/is-missing"; import { PerformerIsMissingCriterionOption } from "./criteria/is-missing";
import { StashIDCriterionOption } from "./criteria/stash-ids";
import { StudiosCriterionOption } from "./criteria/studios"; import { StudiosCriterionOption } from "./criteria/studios";
import { TagsCriterionOption } from "./criteria/tags"; import { TagsCriterionOption } from "./criteria/tags";
import { ListFilterOptions } from "./filter-options"; import { ListFilterOptions } from "./filter-options";
@@ -67,7 +68,6 @@ const stringCriteria: CriterionType[] = [
"tattoos", "tattoos",
"piercings", "piercings",
"aliases", "aliases",
"stash_id",
]; ];
const criterionOptions = [ const criterionOptions = [
@@ -76,6 +76,7 @@ const criterionOptions = [
PerformerIsMissingCriterionOption, PerformerIsMissingCriterionOption,
TagsCriterionOption, TagsCriterionOption,
StudiosCriterionOption, StudiosCriterionOption,
StashIDCriterionOption,
createStringCriterionOption("url"), createStringCriterionOption("url"),
new NullNumberCriterionOption("rating", "rating100"), new NullNumberCriterionOption("rating", "rating100"),
createMandatoryNumberCriterionOption("tag_count"), createMandatoryNumberCriterionOption("tag_count"),

View File

@@ -26,6 +26,7 @@ import {
} from "./criteria/phash"; } from "./criteria/phash";
import { PerformerFavoriteCriterionOption } from "./criteria/favorite"; import { PerformerFavoriteCriterionOption } from "./criteria/favorite";
import { CaptionsCriterionOption } from "./criteria/captions"; import { CaptionsCriterionOption } from "./criteria/captions";
import { StashIDCriterionOption } from "./criteria/stash-ids";
const defaultSortBy = "date"; const defaultSortBy = "date";
const sortByOptions = [ const sortByOptions = [
@@ -82,7 +83,7 @@ const criterionOptions = [
StudiosCriterionOption, StudiosCriterionOption,
MoviesCriterionOption, MoviesCriterionOption,
createStringCriterionOption("url"), createStringCriterionOption("url"),
createStringCriterionOption("stash_id"), StashIDCriterionOption,
InteractiveCriterionOption, InteractiveCriterionOption,
CaptionsCriterionOption, CaptionsCriterionOption,
createMandatoryNumberCriterionOption("interactive_speed"), createMandatoryNumberCriterionOption("interactive_speed"),

View File

@@ -7,6 +7,7 @@ import {
createMandatoryTimestampCriterionOption, createMandatoryTimestampCriterionOption,
} from "./criteria/criterion"; } from "./criteria/criterion";
import { StudioIsMissingCriterionOption } from "./criteria/is-missing"; import { StudioIsMissingCriterionOption } from "./criteria/is-missing";
import { StashIDCriterionOption } from "./criteria/stash-ids";
import { ParentStudiosCriterionOption } from "./criteria/studios"; import { ParentStudiosCriterionOption } from "./criteria/studios";
import { ListFilterOptions } from "./filter-options"; import { ListFilterOptions } from "./filter-options";
import { DisplayMode } from "./types"; import { DisplayMode } from "./types";
@@ -41,7 +42,7 @@ const criterionOptions = [
createMandatoryNumberCriterionOption("image_count"), createMandatoryNumberCriterionOption("image_count"),
createMandatoryNumberCriterionOption("gallery_count"), createMandatoryNumberCriterionOption("gallery_count"),
createStringCriterionOption("url"), createStringCriterionOption("url"),
createStringCriterionOption("stash_id"), StashIDCriterionOption,
createStringCriterionOption("aliases"), createStringCriterionOption("aliases"),
createMandatoryTimestampCriterionOption("created_at"), createMandatoryTimestampCriterionOption("created_at"),
createMandatoryTimestampCriterionOption("updated_at"), createMandatoryTimestampCriterionOption("updated_at"),

View File

@@ -33,6 +33,11 @@ export interface IPHashDuplicationValue {
distance?: number; // currently not implemented distance?: number; // currently not implemented
} }
export interface IStashIDValue {
endpoint: string;
stashID: string;
}
export interface IDateValue { export interface IDateValue {
value: string; value: string;
value2: string | undefined; value2: string | undefined;
@@ -57,6 +62,13 @@ export function criterionIsNumberValue(
return typeof value === "object" && "value" in value && "value2" in value; return typeof value === "object" && "value" in value && "value2" in value;
} }
export function criterionIsStashIDValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any
): value is IStashIDValue {
return typeof value === "object" && "endpoint" in value && "stashID" in value;
}
export function criterionIsDateValue( export function criterionIsDateValue(
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
value: any value: any
@@ -151,6 +163,7 @@ export type CriterionType =
| "duplicated" | "duplicated"
| "ignore_auto_tag" | "ignore_auto_tag"
| "file_count" | "file_count"
| "stash_id_endpoint"
| "date" | "date"
| "created_at" | "created_at"
| "updated_at" | "updated_at"