Skip to content

Commit

Permalink
Add proper dupe validation for scene draft submissions (#502)
Browse files Browse the repository at this point in the history
  • Loading branch information
InfiniteTF committed Sep 21, 2022
1 parent 214c8af commit ac08e65
Show file tree
Hide file tree
Showing 21 changed files with 2,313 additions and 56 deletions.
1,553 changes: 1,553 additions & 0 deletions frontend/src/graphql/definitions/QueryExistingScene.ts

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions frontend/src/graphql/definitions/globalTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,12 @@ export interface PerformerQueryInput {
sort: PerformerSortEnum;
}

export interface QueryExistingSceneInput {
title?: string | null;
studio_id?: string | null;
fingerprints: FingerprintInput[];
}

export interface ResetPasswordInput {
email: string;
}
Expand Down
12 changes: 12 additions & 0 deletions frontend/src/graphql/queries/QueryExistingScene.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#import "../fragments/EditFragment.gql"
#import "../fragments/SceneFragment.gql"
query QueryExistingScene($input: QueryExistingSceneInput!) {
queryExistingScene(input: $input) {
scenes {
...SceneFragment
}
edits {
...EditFragment
}
}
}
17 changes: 17 additions & 0 deletions frontend/src/graphql/queries/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ import { Site, SiteVariables } from "../definitions/Site";
import { Sites } from "../definitions/Sites";
import { Draft, DraftVariables } from "../definitions/Draft";
import { Drafts } from "../definitions/Drafts";
import {
QueryExistingScene,
QueryExistingSceneVariables,
} from "../definitions/QueryExistingScene";

import CategoryQuery from "./Category.gql";
import CategoriesQuery from "./Categories.gql";
Expand Down Expand Up @@ -80,6 +84,7 @@ import SiteQuery from "./Site.gql";
import SitesQuery from "./Sites.gql";
import DraftQuery from "./Draft.gql";
import DraftsQuery from "./Drafts.gql";
import QueryExistingSceneQuery from "./QueryExistingScene.gql";

export const useCategory = (variables: CategoryVariables, skip = false) =>
useQuery<Category, CategoryVariables>(CategoryQuery, {
Expand Down Expand Up @@ -253,3 +258,15 @@ export const useDraft = (variables: DraftVariables, skip = false) =>
});

export const useDrafts = () => useQuery<Drafts>(DraftsQuery);

export const useQueryExistingScene = (
variables: QueryExistingSceneVariables,
skip = false
) =>
useQuery<QueryExistingScene, QueryExistingSceneVariables>(
QueryExistingSceneQuery,
{
variables,
skip,
}
);
64 changes: 11 additions & 53 deletions frontend/src/pages/drafts/SceneDraft.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { FC, useState } from "react";
import { useHistory, Link } from "react-router-dom";
import { Alert, Col, Row } from "react-bootstrap";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";

import { sceneHref } from "src/utils/route";
import {
Expand All @@ -13,13 +12,9 @@ import {
useSceneEdit,
OperationEnum,
SceneEditDetailsInput,
useScenesWithoutCount,
CriterionModifier,
SortDirectionEnum,
SceneSortEnum,
FingerprintAlgorithm,
} from "src/graphql";
import { Icon, LoadingIndicator } from "src/components/fragments";
import { LoadingIndicator } from "src/components/fragments";
import { editHref } from "src/utils";
import { parseSceneDraft } from "./parse";

Expand All @@ -44,21 +39,6 @@ const SceneDraftAdd: FC<Props> = ({ draft }) => {
{ id: draft.data.id ?? "" },
!isUpdate
);
const { data: fingerprintMatches } = useScenesWithoutCount(
{
input: {
fingerprints: {
modifier: CriterionModifier.INCLUDES,
value: draft.data.fingerprints.map((f) => f.hash),
},
page: 1,
per_page: 100,
direction: SortDirectionEnum.DESC,
sort: SceneSortEnum.CREATED_AT,
},
},
isUpdate
);

const doInsert = (updateData: SceneEditDetailsInput, editNote: string) => {
const details: SceneEditDetailsInput = {
Expand Down Expand Up @@ -98,8 +78,6 @@ const SceneDraftAdd: FC<Props> = ({ draft }) => {
</li>
));

const existingScenes = fingerprintMatches?.queryScenes?.scenes ?? [];

const phashMissing =
!isUpdate &&
draft.data.fingerprints.filter(
Expand All @@ -123,43 +101,23 @@ const SceneDraftAdd: FC<Props> = ({ draft }) => {
<hr />
</>
)}
<Row>
<Col xs={9}>
{existingScenes.length > 0 && (
{phashMissing && (
<Row>
<Col xs={9}>
<Alert variant="warning">
<div className="mb-2">
<b>Warning</b>: Scenes already exist in the database with the
same fingerprint.
</div>
{existingScenes.map((s) => (
<div key={s.id}>
<Icon icon={faExclamationTriangle} color="red" />
<Link to={sceneHref(s)} className="ms-2">
<b>{s.title}</b>
</Link>
</div>
))}
<div className="mt-2">
Please verify your draft is not already in the database before
submitting.
</div>
<b>Warning</b>: The draft does not include a perceptual hash
(PHASH) for your scene, so it might not pass voting.
</Alert>
)}
{phashMissing && (
<>
<Alert variant="warning">
<b>Warning</b>: The draft does not include a perceptual hash
(PHASH) for your scene, so it might not pass voting.
</Alert>
</>
)}
</Col>
</Row>
</Col>
</Row>
)}
<SceneForm
scene={scene?.findScene ?? undefined}
initial={initialScene}
callback={doInsert}
saving={saving}
isCreate={!isUpdate}
draftFingerprints={draft.data.fingerprints}
/>
{submissionError && (
<div className="text-danger text-end col-9">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/scenes/SceneAdd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const SceneAdd: FC = () => {
<div>
<h3>Add new scene</h3>
<hr />
<SceneForm callback={doInsert} saving={saving} />
<SceneForm callback={doInsert} saving={saving} isCreate />
{submissionError && (
<div className="text-danger text-end col-9">
Error: {submissionError}
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/pages/scenes/sceneForm/ExistingSceneAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { FC } from "react";
import { Alert } from "react-bootstrap";
import { Link } from "react-router-dom";
import { faExclamationTriangle } from "@fortawesome/free-solid-svg-icons";
import { FingerprintAlgorithm, useQueryExistingScene } from "src/graphql";
import { Icon } from "src/components/fragments";
import { sceneHref, editHref } from "src/utils";

interface Props {
title: string | null;
studio_id: string | null;
fingerprints:
| {
hash: string;
algorithm: FingerprintAlgorithm;
duration: number;
}[]
| undefined;
}

const ExistingSceneAlert: FC<Props> = ({
title,
studio_id,
fingerprints = [],
}) => {
const { data: existingData } = useQueryExistingScene({
input: { title, studio_id, fingerprints },
});
const existingScenes = existingData?.queryExistingScene.scenes ?? [];
const existingEdits = existingData?.queryExistingScene.edits ?? [];

if (existingScenes.length === 0 && existingEdits.length === 0) return null;

return (
<Alert variant="warning">
<div className="mb-2">
<b>Warning: Scene match found</b>
</div>

{existingScenes.length > 0 && (
<div className="mb-2">
<span>Existing scenes that have the same title or fingerprints:</span>
{existingScenes.map((s) => (
<div key={s.id}>
<Icon icon={faExclamationTriangle} color="red" />
<Link to={sceneHref(s)} className="ms-2">
<b>{s.title}</b>
</Link>
</div>
))}
</div>
)}

{existingEdits.length > 0 && (
<div className="mb-2">
<span>
Pending edits that submit scenes with the same title or
fingerprints:
</span>
{existingEdits.map((e) => (
<div key={e.id}>
<Icon icon={faExclamationTriangle} color="red" />
<Link to={editHref(e)} className="ms-2">
<b>{(e.details as { title: string }).title}</b>
</Link>
</div>
))}
</div>
)}

<div>
Please verify your draft is not already in the database before
submitting.
</div>
</Alert>
);
};

export default ExistingSceneAlert;
28 changes: 27 additions & 1 deletion frontend/src/pages/scenes/sceneForm/SceneForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ValidSiteTypeEnum,
SceneEditDetailsInput,
GenderEnum,
FingerprintAlgorithm,
} from "src/graphql";

import { renderSceneDetails } from "src/components/editCard/ModifyEdit";
Expand All @@ -31,6 +32,7 @@ import URLInput from "src/components/urlInput";
import DiffScene from "./diff";
import { SceneSchema, SceneFormData } from "./schema";
import { InitialScene } from "./types";
import ExistingSceneAlert from "./ExistingSceneAlert";

const CLASS_NAME = "SceneForm";
const CLASS_NAME_PERFORMER_CHANGE = `${CLASS_NAME}-performer-change`;
Expand All @@ -40,9 +42,22 @@ interface SceneProps {
initial?: InitialScene;
callback: (updateData: SceneEditDetailsInput, editNote: string) => void;
saving: boolean;
isCreate?: boolean;
draftFingerprints?: {
hash: string;
algorithm: FingerprintAlgorithm;
duration: number;
}[];
}

const SceneForm: FC<SceneProps> = ({ scene, initial, callback, saving }) => {
const SceneForm: FC<SceneProps> = ({
scene,
initial,
callback,
saving,
isCreate = false,
draftFingerprints,
}) => {
const {
register,
control,
Expand Down Expand Up @@ -252,6 +267,17 @@ const SceneForm: FC<SceneProps> = ({ scene, initial, callback, saving }) => {

return (
<Form className={CLASS_NAME} onSubmit={handleSubmit(onSubmit)}>
{isCreate && (
<Row>
<Col xs={9}>
<ExistingSceneAlert
title={fieldData.title}
studio_id={fieldData.studio?.id}
fingerprints={draftFingerprints}
/>
</Col>
</Row>
)}
<Tabs activeKey={activeTab} onSelect={(key) => key && setActiveTab(key)}>
<Tab eventKey="details" title="Details" className="col-xl-9">
<Row>
Expand Down
3 changes: 3 additions & 0 deletions graphql/schema/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ type Query {
findDraft(id: ID!): Draft @hasRole(role: READ)
findDrafts: [Draft!]! @hasRole(role: READ)

###Find scenes or pending scenes which match scene input###
queryExistingScene(input: QueryExistingSceneInput!): QueryExistingSceneResult! @hasRole(role: READ)

#### Version ####
version: Version! @hasRole(role: READ)

Expand Down
11 changes: 11 additions & 0 deletions graphql/schema/types/scene.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,14 @@ input SceneDraftInput {
image: Upload
fingerprints: [FingerprintInput!]!
}

input QueryExistingSceneInput {
title: String
studio_id: ID
fingerprints: [FingerprintInput!]!
}

type QueryExistingSceneResult {
edits: [Edit!]!
scenes: [Scene!]!
}
3 changes: 3 additions & 0 deletions pkg/api/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ func (r *Resolver) PerformerDraft() models.PerformerDraftResolver {
func (r *Resolver) SceneDraft() models.SceneDraftResolver {
return &sceneDraftResolver{r}
}
func (r *Resolver) QueryExistingSceneResult() models.QueryExistingSceneResultResolver {
return &queryExistingSceneResolver{r}
}

type mutationResolver struct{ *Resolver }

Expand Down
20 changes: 20 additions & 0 deletions pkg/api/resolver_query_find_scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,23 @@ func (r *querySceneResolver) Scenes(ctx context.Context, obj *models.SceneQuery)
u := user.GetCurrentUser(ctx)
return qb.QueryScenes(obj.Filter, u.ID)
}

func (r *queryResolver) QueryExistingScene(ctx context.Context, input models.QueryExistingSceneInput) (*models.QueryExistingSceneResult, error) {
return &models.QueryExistingSceneResult{
Input: input,
}, nil
}

type queryExistingSceneResolver struct{ *Resolver }

func (r *queryExistingSceneResolver) Edits(ctx context.Context, obj *models.QueryExistingSceneResult) ([]*models.Edit, error) {
fac := r.getRepoFactory(ctx)
qb := fac.Edit()
return qb.FindPendingSceneCreation(obj.Input)
}

func (r *queryExistingSceneResolver) Scenes(ctx context.Context, obj *models.QueryExistingSceneResult) ([]*models.Scene, error) {
fac := r.getRepoFactory(ctx)
qb := fac.Scene()
return qb.FindExistingScenes(obj.Input)
}
2 changes: 1 addition & 1 deletion pkg/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"github.com/jmoiron/sqlx"
)

var appSchemaVersion uint = 28
var appSchemaVersion uint = 29

var databaseProviders map[string]databaseProvider

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
CREATE INDEX scene_edit_fingerprint_added_idx ON edits USING GIN
(jsonb_path_query_array(data, '$.new_data.added_fingerprints[*].hash'))
WHERE target_type = 'SCENE';
1 change: 1 addition & 0 deletions pkg/models/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ type EditRepo interface {
FindByStudioID(id uuid.UUID) ([]*Edit, error)
FindBySceneID(id uuid.UUID) ([]*Edit, error)
FindCompletedEdits(int, int, int) ([]*Edit, error)
FindPendingSceneCreation(input QueryExistingSceneInput) ([]*Edit, error)
}
Loading

0 comments on commit ac08e65

Please sign in to comment.