mirror of
https://github.com/BillyOutlast/stash-box.git
synced 2026-02-04 02:51:17 +01:00
Add report fingerprint functionality (#705)
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { FC } from "react";
|
||||
import cx from "classnames";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
@@ -7,13 +8,14 @@ interface Props {
|
||||
className?: string;
|
||||
color?: string;
|
||||
title?: string;
|
||||
variant?: "danger" | "success" | "info" | "warning";
|
||||
}
|
||||
|
||||
const Icon: FC<Props> = ({ icon, className, color, title }) => (
|
||||
const Icon: FC<Props> = ({ icon, className, color, title, variant }) => (
|
||||
<FontAwesomeIcon
|
||||
title={title}
|
||||
icon={icon}
|
||||
className={`fa-icon ${className}`}
|
||||
className={cx("fa-icon", className, { [`text-${variant}`]: variant })}
|
||||
color={color}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,9 @@ fragment SceneFragment on Scene {
|
||||
algorithm
|
||||
duration
|
||||
submissions
|
||||
reports
|
||||
user_submitted
|
||||
user_reported
|
||||
created
|
||||
updated
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ mutation UnmatchFingerprint(
|
||||
) {
|
||||
unmatchFingerprint: submitFingerprint(
|
||||
input: {
|
||||
unmatch: true
|
||||
vote: REMOVE
|
||||
scene_id: $scene_id
|
||||
fingerprint: { hash: $hash, algorithm: $algorithm, duration: $duration }
|
||||
}
|
||||
|
||||
@@ -293,8 +293,14 @@ export type Fingerprint = {
|
||||
created: Scalars["Time"]["output"];
|
||||
duration: Scalars["Int"]["output"];
|
||||
hash: Scalars["String"]["output"];
|
||||
/** number of times this fingerprint has been reported */
|
||||
reports: Scalars["Int"]["output"];
|
||||
/** number of times this fingerprint has been submitted (excluding reports) */
|
||||
submissions: Scalars["Int"]["output"];
|
||||
updated: Scalars["Time"]["output"];
|
||||
/** true if the current user reported this fingerprint */
|
||||
user_reported: Scalars["Boolean"]["output"];
|
||||
/** true if the current user submitted this fingerprint */
|
||||
user_submitted: Scalars["Boolean"]["output"];
|
||||
};
|
||||
|
||||
@@ -332,9 +338,20 @@ export type FingerprintQueryInput = {
|
||||
export type FingerprintSubmission = {
|
||||
fingerprint: FingerprintInput;
|
||||
scene_id: Scalars["ID"]["input"];
|
||||
/** @deprecated Use `vote` with REMOVE instead */
|
||||
unmatch?: InputMaybe<Scalars["Boolean"]["input"]>;
|
||||
vote?: InputMaybe<FingerprintSubmissionType>;
|
||||
};
|
||||
|
||||
export enum FingerprintSubmissionType {
|
||||
/** Report as invalid */
|
||||
INVALID = "INVALID",
|
||||
/** Remove vote */
|
||||
REMOVE = "REMOVE",
|
||||
/** Positive vote */
|
||||
VALID = "VALID",
|
||||
}
|
||||
|
||||
export type FuzzyDate = {
|
||||
__typename: "FuzzyDate";
|
||||
accuracy: DateAccuracyEnum;
|
||||
@@ -2076,7 +2093,9 @@ export type EditFragment = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -2852,7 +2871,9 @@ export type EditFragment = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -3054,7 +3075,9 @@ export type SceneFragment = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -3418,7 +3441,9 @@ export type ApplyEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -4249,7 +4274,9 @@ export type ApplyEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -4645,7 +4672,9 @@ export type PerformerEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -5476,7 +5505,9 @@ export type PerformerEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -5682,7 +5713,9 @@ export type PerformerEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -6513,7 +6546,9 @@ export type PerformerEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -6763,7 +6798,9 @@ export type SceneEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -7594,7 +7631,9 @@ export type SceneEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -7800,7 +7839,9 @@ export type SceneEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -8631,7 +8672,9 @@ export type SceneEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -8836,7 +8879,9 @@ export type StudioEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -9667,7 +9712,9 @@ export type StudioEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -9873,7 +9920,9 @@ export type StudioEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -10704,7 +10753,9 @@ export type StudioEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -10909,7 +10960,9 @@ export type TagEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -11740,7 +11793,9 @@ export type TagEditMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -11946,7 +12001,9 @@ export type TagEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -12777,7 +12834,9 @@ export type TagEditUpdateMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -13126,7 +13185,9 @@ export type VoteMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -13957,7 +14018,9 @@ export type VoteMutation = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -14422,7 +14485,9 @@ export type EditQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -15253,7 +15318,9 @@ export type EditQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -15452,7 +15519,9 @@ export type EditUpdateQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -15903,7 +15972,9 @@ export type EditsQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -16754,7 +16825,9 @@ export type EditsQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -17105,7 +17178,9 @@ export type QueryExistingSceneQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -17256,7 +17331,9 @@ export type QueryExistingSceneQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -18107,7 +18184,9 @@ export type QueryExistingSceneQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -18225,7 +18304,9 @@ export type SceneQuery = {
|
||||
algorithm: FingerprintAlgorithm;
|
||||
duration: number;
|
||||
submissions: number;
|
||||
reports: number;
|
||||
user_submitted: boolean;
|
||||
user_reported: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
}>;
|
||||
@@ -19272,10 +19353,15 @@ export const SceneFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -20882,10 +20968,15 @@ export const EditFragmentDoc = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -22126,10 +22217,15 @@ export const ApplyEditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -24609,10 +24705,15 @@ export const PerformerEditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -26220,10 +26321,15 @@ export const PerformerEditUpdateDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -28017,10 +28123,15 @@ export const SceneEditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -29625,10 +29736,15 @@ export const SceneEditUpdateDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -31220,10 +31336,15 @@ export const StudioEditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -32828,10 +32949,15 @@ export const StudioEditUpdateDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -34423,10 +34549,15 @@ export const TagEditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -36031,10 +36162,15 @@ export const TagEditUpdateDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -37281,8 +37417,8 @@ export const UnmatchFingerprintDocument = {
|
||||
fields: [
|
||||
{
|
||||
kind: "ObjectField",
|
||||
name: { kind: "Name", value: "unmatch" },
|
||||
value: { kind: "BooleanValue", value: true },
|
||||
name: { kind: "Name", value: "vote" },
|
||||
value: { kind: "EnumValue", value: "REMOVE" },
|
||||
},
|
||||
{
|
||||
kind: "ObjectField",
|
||||
@@ -38302,10 +38438,15 @@ export const VoteDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -40749,10 +40890,15 @@ export const EditDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -42920,10 +43066,15 @@ export const EditUpdateDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -43390,10 +43541,15 @@ export const EditsDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -45865,10 +46021,15 @@ export const QueryExistingSceneDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
@@ -47250,10 +47411,15 @@ export const SceneDocument = {
|
||||
{ kind: "Field", name: { kind: "Name", value: "algorithm" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "duration" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "submissions" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "reports" } },
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_submitted" },
|
||||
},
|
||||
{
|
||||
kind: "Field",
|
||||
name: { kind: "Name", value: "user_reported" },
|
||||
},
|
||||
{ kind: "Field", name: { kind: "Name", value: "created" } },
|
||||
{ kind: "Field", name: { kind: "Name", value: "updated" } },
|
||||
],
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { FC, useContext } from "react";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { Button, Card, Tabs, Tab, Table } from "react-bootstrap";
|
||||
import {
|
||||
faCheckCircle,
|
||||
faTimesCircle,
|
||||
faSpinner,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { Button, Card, Tabs, Tab } from "react-bootstrap";
|
||||
|
||||
import {
|
||||
usePendingEditsCount,
|
||||
TargetTypeEnum,
|
||||
useUnmatchFingerprint,
|
||||
SceneFragment as Scene,
|
||||
} from "src/graphql";
|
||||
import AuthContext from "src/AuthContext";
|
||||
import { useToast } from "src/hooks";
|
||||
import {
|
||||
canEdit,
|
||||
tagHref,
|
||||
@@ -22,26 +15,15 @@ import {
|
||||
studioHref,
|
||||
createHref,
|
||||
formatDuration,
|
||||
formatDateTime,
|
||||
formatPendingEdits,
|
||||
getUrlBySite,
|
||||
compareByName,
|
||||
} from "src/utils";
|
||||
import {
|
||||
ROUTE_SCENE_EDIT,
|
||||
ROUTE_SCENES,
|
||||
ROUTE_SCENE_DELETE,
|
||||
} from "src/constants/route";
|
||||
import {
|
||||
GenderIcon,
|
||||
TagLink,
|
||||
PerformerName,
|
||||
Icon,
|
||||
} from "src/components/fragments";
|
||||
import { ROUTE_SCENE_EDIT, ROUTE_SCENE_DELETE } from "src/constants/route";
|
||||
import { GenderIcon, TagLink, PerformerName } from "src/components/fragments";
|
||||
import { EditList, URLList } from "src/components/list";
|
||||
import Image from "src/components/image";
|
||||
|
||||
type Fingerprint = NonNullable<Scene["fingerprints"][number]>;
|
||||
import { FingerprintTable } from "./components/fingerprints";
|
||||
|
||||
const DEFAULT_TAB = "description";
|
||||
|
||||
@@ -54,9 +36,6 @@ const SceneComponent: FC<Props> = ({ scene }) => {
|
||||
const location = useLocation();
|
||||
const activeTab = location.hash?.slice(1) || DEFAULT_TAB;
|
||||
const auth = useContext(AuthContext);
|
||||
const addToast = useToast();
|
||||
|
||||
const [unmatchFingerprint, { loading: unmatching }] = useUnmatchFingerprint();
|
||||
|
||||
const { data: editData } = usePendingEditsCount({
|
||||
type: TargetTypeEnum.SCENE,
|
||||
@@ -83,72 +62,6 @@ const SceneComponent: FC<Props> = ({ scene }) => {
|
||||
})
|
||||
.map((p, index) => (index % 2 === 2 ? [" • ", p] : p));
|
||||
|
||||
async function handleFingerprintUnmatch(fingerprint: Fingerprint) {
|
||||
if (unmatching) return;
|
||||
|
||||
const { data } = await unmatchFingerprint({
|
||||
variables: {
|
||||
scene_id: scene.id,
|
||||
algorithm: fingerprint.algorithm,
|
||||
hash: fingerprint.hash,
|
||||
duration: fingerprint.duration,
|
||||
},
|
||||
});
|
||||
const success = data?.unmatchFingerprint;
|
||||
addToast({
|
||||
variant: success ? "success" : "danger",
|
||||
content: `${
|
||||
success ? "Removed" : "Failed to remove"
|
||||
} fingerprint submission`,
|
||||
});
|
||||
}
|
||||
|
||||
function maybeRenderSubmitted(fingerprint: Fingerprint) {
|
||||
if (fingerprint.user_submitted) {
|
||||
return (
|
||||
<Button
|
||||
className="user-submitted"
|
||||
title="Submitted by you - click to remove submission"
|
||||
onKeyDown={() => handleFingerprintUnmatch(fingerprint)}
|
||||
onClick={() => handleFingerprintUnmatch(fingerprint)}
|
||||
variant="link"
|
||||
>
|
||||
{!unmatching ? (
|
||||
<>
|
||||
<Icon icon={faCheckCircle} />
|
||||
<Icon icon={faTimesCircle} />
|
||||
</>
|
||||
) : (
|
||||
<Icon icon={faSpinner} className="fa-spin" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fingerprints = scene.fingerprints.map((fingerprint) => (
|
||||
<tr key={fingerprint.hash}>
|
||||
<td>{fingerprint.algorithm}</td>
|
||||
<td className="font-monospace">
|
||||
<Link
|
||||
to={`${createHref(ROUTE_SCENES)}?fingerprint=${fingerprint.hash}`}
|
||||
>
|
||||
{fingerprint.hash}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<span title={`${fingerprint.duration}s`}>
|
||||
{formatDuration(fingerprint.duration)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{fingerprint.submissions}
|
||||
{maybeRenderSubmitted(fingerprint)}
|
||||
</td>
|
||||
<td>{formatDateTime(fingerprint.created)}</td>
|
||||
<td>{formatDateTime(fingerprint.updated)}</td>
|
||||
</tr>
|
||||
));
|
||||
const tags = [...scene.tags].sort(compareByName).map((tag) => (
|
||||
<li key={tag.name}>
|
||||
<TagLink
|
||||
@@ -262,38 +175,7 @@ const SceneComponent: FC<Props> = ({ scene }) => {
|
||||
)}
|
||||
</Tab>
|
||||
<Tab eventKey="fingerprints" title="Fingerprints" mountOnEnter={false}>
|
||||
<div className="scene-fingerprints my-4">
|
||||
<h4>Fingerprints:</h4>
|
||||
{fingerprints.length === 0 ? (
|
||||
<h6>No fingerprints found for this scene.</h6>
|
||||
) : (
|
||||
<Table striped variant="dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Algorithm</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Hash</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Duration</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Submissions</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>First Added</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Last Added</b>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{fingerprints}</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
<FingerprintTable scene={scene} />
|
||||
</Tab>
|
||||
<Tab eventKey="links" title="Links">
|
||||
<URLList urls={scene.urls} />
|
||||
|
||||
146
frontend/src/pages/scenes/components/fingerprints.tsx
Normal file
146
frontend/src/pages/scenes/components/fingerprints.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import { FC } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Button, Table } from "react-bootstrap";
|
||||
import {
|
||||
faCheckCircle,
|
||||
faTimesCircle,
|
||||
faSpinner,
|
||||
faTriangleExclamation,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { Fingerprint, useUnmatchFingerprint } from "src/graphql";
|
||||
import { useToast } from "src/hooks";
|
||||
import { createHref, formatDate, formatDuration } from "src/utils";
|
||||
import { ROUTE_SCENES } from "src/constants/route";
|
||||
import { Icon } from "src/components/fragments";
|
||||
|
||||
interface Props {
|
||||
scene: {
|
||||
id: string;
|
||||
fingerprints: Fingerprint[];
|
||||
};
|
||||
}
|
||||
|
||||
type MatchType = "submission" | "report";
|
||||
|
||||
export const FingerprintTable: FC<Props> = ({ scene }) => {
|
||||
const addToast = useToast();
|
||||
|
||||
const [unmatchFingerprint, { loading: unmatching }] = useUnmatchFingerprint();
|
||||
|
||||
const handleFingerprintUnmatch = async (
|
||||
fingerprint: Fingerprint,
|
||||
type: MatchType,
|
||||
) => {
|
||||
if (unmatching) return;
|
||||
|
||||
const { data } = await unmatchFingerprint({
|
||||
variables: {
|
||||
scene_id: scene.id,
|
||||
algorithm: fingerprint.algorithm,
|
||||
hash: fingerprint.hash,
|
||||
duration: fingerprint.duration,
|
||||
},
|
||||
});
|
||||
const success = data?.unmatchFingerprint;
|
||||
addToast({
|
||||
variant: success ? "success" : "danger",
|
||||
content: `${
|
||||
success ? "Removed" : "Failed to remove"
|
||||
} fingerprint ${type}`,
|
||||
});
|
||||
};
|
||||
|
||||
const renderUnmatch = (fingerprint: Fingerprint, type: MatchType) => (
|
||||
<Button
|
||||
className="user-submitted"
|
||||
title={`Remove ${type}`}
|
||||
onKeyDown={() => handleFingerprintUnmatch(fingerprint, type)}
|
||||
onClick={() => handleFingerprintUnmatch(fingerprint, type)}
|
||||
variant="link"
|
||||
disabled={unmatching}
|
||||
>
|
||||
{!unmatching ? (
|
||||
<>
|
||||
<Icon icon={faCheckCircle} />
|
||||
<Icon icon={faTimesCircle} />
|
||||
</>
|
||||
) : (
|
||||
<Icon icon={faSpinner} className="fa-spin" />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="scene-fingerprints my-4">
|
||||
<h4>Fingerprints:</h4>
|
||||
{scene.fingerprints.length === 0 ? (
|
||||
<h6>No fingerprints found for this scene.</h6>
|
||||
) : (
|
||||
<Table striped variant="dark">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>
|
||||
<b>Algorithm</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Hash</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Duration</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Submissions</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Reports</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>First Added</b>
|
||||
</td>
|
||||
<td>
|
||||
<b>Last Added</b>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{scene.fingerprints.map((fingerprint) => (
|
||||
<tr key={fingerprint.hash}>
|
||||
<td>{fingerprint.algorithm}</td>
|
||||
<td className="font-monospace">
|
||||
<Link
|
||||
to={`${createHref(ROUTE_SCENES)}?fingerprint=${fingerprint.hash}`}
|
||||
>
|
||||
{fingerprint.hash}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
<span title={`${fingerprint.duration}s`}>
|
||||
{formatDuration(fingerprint.duration)}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{fingerprint.submissions}
|
||||
{fingerprint.user_submitted &&
|
||||
renderUnmatch(fingerprint, "submission")}
|
||||
</td>
|
||||
<td>
|
||||
{fingerprint.reports > 0 && (
|
||||
<>
|
||||
{fingerprint.reports}{" "}
|
||||
<Icon icon={faTriangleExclamation} variant="danger" />
|
||||
{fingerprint.user_reported &&
|
||||
renderUnmatch(fingerprint, "report")}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td>{formatDate(fingerprint.created)}</td>
|
||||
<td>{formatDate(fingerprint.updated)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -13,6 +13,22 @@ export const formatDateTime = (dateTime: Date | string, utc = false) => {
|
||||
})}`;
|
||||
};
|
||||
|
||||
export const formatDate = (dateTime: Date | string, utc = false) => {
|
||||
const timeZone = utc ? "UTC" : undefined;
|
||||
const date = dateTime instanceof Date ? dateTime : new Date(dateTime);
|
||||
return date.toLocaleString("en-us", {
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
day: "numeric",
|
||||
timeZone,
|
||||
});
|
||||
};
|
||||
|
||||
export const formatISODate = (dateTime: Date | string) => {
|
||||
const date = dateTime instanceof Date ? dateTime : new Date(dateTime);
|
||||
return date.toISOString().slice(0, 10);
|
||||
};
|
||||
|
||||
export const isValidDate = (date?: string) => !date || isValid(parseISO(date));
|
||||
|
||||
export const dateWithinRange = (
|
||||
|
||||
@@ -22,14 +22,29 @@ enum FavoriteFilter {
|
||||
ALL
|
||||
}
|
||||
|
||||
enum FingerprintSubmissionType {
|
||||
"Positive vote"
|
||||
VALID
|
||||
"Report as invalid"
|
||||
INVALID
|
||||
"Remove vote"
|
||||
REMOVE
|
||||
}
|
||||
|
||||
type Fingerprint {
|
||||
hash: String!
|
||||
algorithm: FingerprintAlgorithm!
|
||||
duration: Int!
|
||||
"number of times this fingerprint has been submitted (excluding reports)"
|
||||
submissions: Int!
|
||||
"number of times this fingerprint has been reported"
|
||||
reports: Int!
|
||||
created: Time!
|
||||
updated: Time!
|
||||
"true if the current user submitted this fingerprint"
|
||||
user_submitted: Boolean!
|
||||
"true if the current user reported this fingerprint"
|
||||
user_reported: Boolean!
|
||||
}
|
||||
|
||||
type DraftFingerprint {
|
||||
@@ -64,7 +79,8 @@ input FingerprintQueryInput {
|
||||
input FingerprintSubmission {
|
||||
scene_id: ID!
|
||||
fingerprint: FingerprintInput!
|
||||
unmatch: Boolean
|
||||
unmatch: Boolean @deprecated(reason: "Use `vote` with REMOVE instead")
|
||||
vote: FingerprintSubmissionType = VALID
|
||||
}
|
||||
|
||||
type Scene {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
var appSchemaVersion uint = 39
|
||||
var appSchemaVersion uint = 40
|
||||
|
||||
var databaseProviders map[string]databaseProvider
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "scene_fingerprints"
|
||||
ADD COLUMN "vote" SMALLINT NOT NULL DEFAULT 1 CHECK (vote = -1 OR vote = 1);
|
||||
@@ -142,8 +142,10 @@ type ComplexityRoot struct {
|
||||
Created func(childComplexity int) int
|
||||
Duration func(childComplexity int) int
|
||||
Hash func(childComplexity int) int
|
||||
Reports func(childComplexity int) int
|
||||
Submissions func(childComplexity int) int
|
||||
Updated func(childComplexity int) int
|
||||
UserReported func(childComplexity int) int
|
||||
UserSubmitted func(childComplexity int) int
|
||||
}
|
||||
|
||||
@@ -1244,6 +1246,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Fingerprint.Hash(childComplexity), true
|
||||
|
||||
case "Fingerprint.reports":
|
||||
if e.complexity.Fingerprint.Reports == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Fingerprint.Reports(childComplexity), true
|
||||
|
||||
case "Fingerprint.submissions":
|
||||
if e.complexity.Fingerprint.Submissions == nil {
|
||||
break
|
||||
@@ -1258,6 +1267,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Fingerprint.Updated(childComplexity), true
|
||||
|
||||
case "Fingerprint.user_reported":
|
||||
if e.complexity.Fingerprint.UserReported == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.Fingerprint.UserReported(childComplexity), true
|
||||
|
||||
case "Fingerprint.user_submitted":
|
||||
if e.complexity.Fingerprint.UserSubmitted == nil {
|
||||
break
|
||||
@@ -5003,14 +5019,29 @@ enum FavoriteFilter {
|
||||
ALL
|
||||
}
|
||||
|
||||
enum FingerprintSubmissionType {
|
||||
"Positive vote"
|
||||
VALID
|
||||
"Report as invalid"
|
||||
INVALID
|
||||
"Remove vote"
|
||||
REMOVE
|
||||
}
|
||||
|
||||
type Fingerprint {
|
||||
hash: String!
|
||||
algorithm: FingerprintAlgorithm!
|
||||
duration: Int!
|
||||
"number of times this fingerprint has been submitted (excluding reports)"
|
||||
submissions: Int!
|
||||
"number of times this fingerprint has been reported"
|
||||
reports: Int!
|
||||
created: Time!
|
||||
updated: Time!
|
||||
"true if the current user submitted this fingerprint"
|
||||
user_submitted: Boolean!
|
||||
"true if the current user reported this fingerprint"
|
||||
user_reported: Boolean!
|
||||
}
|
||||
|
||||
type DraftFingerprint {
|
||||
@@ -5045,7 +5076,8 @@ input FingerprintQueryInput {
|
||||
input FingerprintSubmission {
|
||||
scene_id: ID!
|
||||
fingerprint: FingerprintInput!
|
||||
unmatch: Boolean
|
||||
unmatch: Boolean @deprecated(reason: "Use ` + "`" + `vote` + "`" + ` with REMOVE instead")
|
||||
vote: FingerprintSubmissionType = VALID
|
||||
}
|
||||
|
||||
type Scene {
|
||||
@@ -10931,6 +10963,50 @@ func (ec *executionContext) fieldContext_Fingerprint_submissions(_ context.Conte
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Fingerprint_reports(ctx context.Context, field graphql.CollectedField, obj *Fingerprint) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Fingerprint_reports(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.Reports, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(int)
|
||||
fc.Result = res
|
||||
return ec.marshalNInt2int(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Fingerprint_reports(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Fingerprint",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Int does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Fingerprint_created(ctx context.Context, field graphql.CollectedField, obj *Fingerprint) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Fingerprint_created(ctx, field)
|
||||
if err != nil {
|
||||
@@ -11063,6 +11139,50 @@ func (ec *executionContext) fieldContext_Fingerprint_user_submitted(_ context.Co
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Fingerprint_user_reported(ctx context.Context, field graphql.CollectedField, obj *Fingerprint) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Fingerprint_user_reported(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.UserReported, nil
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return graphql.Null
|
||||
}
|
||||
if resTmp == nil {
|
||||
if !graphql.HasFieldError(ctx, fc) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(bool)
|
||||
fc.Result = res
|
||||
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Fingerprint_user_reported(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "Fingerprint",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type Boolean does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _FuzzyDate_date(ctx context.Context, field graphql.CollectedField, obj *FuzzyDate) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_FuzzyDate_date(ctx, field)
|
||||
if err != nil {
|
||||
@@ -25704,12 +25824,16 @@ func (ec *executionContext) fieldContext_Scene_fingerprints(ctx context.Context,
|
||||
return ec.fieldContext_Fingerprint_duration(ctx, field)
|
||||
case "submissions":
|
||||
return ec.fieldContext_Fingerprint_submissions(ctx, field)
|
||||
case "reports":
|
||||
return ec.fieldContext_Fingerprint_reports(ctx, field)
|
||||
case "created":
|
||||
return ec.fieldContext_Fingerprint_created(ctx, field)
|
||||
case "updated":
|
||||
return ec.fieldContext_Fingerprint_updated(ctx, field)
|
||||
case "user_submitted":
|
||||
return ec.fieldContext_Fingerprint_user_submitted(ctx, field)
|
||||
case "user_reported":
|
||||
return ec.fieldContext_Fingerprint_user_reported(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Fingerprint", field.Name)
|
||||
},
|
||||
@@ -27245,12 +27369,16 @@ func (ec *executionContext) fieldContext_SceneEdit_added_fingerprints(_ context.
|
||||
return ec.fieldContext_Fingerprint_duration(ctx, field)
|
||||
case "submissions":
|
||||
return ec.fieldContext_Fingerprint_submissions(ctx, field)
|
||||
case "reports":
|
||||
return ec.fieldContext_Fingerprint_reports(ctx, field)
|
||||
case "created":
|
||||
return ec.fieldContext_Fingerprint_created(ctx, field)
|
||||
case "updated":
|
||||
return ec.fieldContext_Fingerprint_updated(ctx, field)
|
||||
case "user_submitted":
|
||||
return ec.fieldContext_Fingerprint_user_submitted(ctx, field)
|
||||
case "user_reported":
|
||||
return ec.fieldContext_Fingerprint_user_reported(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Fingerprint", field.Name)
|
||||
},
|
||||
@@ -27302,12 +27430,16 @@ func (ec *executionContext) fieldContext_SceneEdit_removed_fingerprints(_ contex
|
||||
return ec.fieldContext_Fingerprint_duration(ctx, field)
|
||||
case "submissions":
|
||||
return ec.fieldContext_Fingerprint_submissions(ctx, field)
|
||||
case "reports":
|
||||
return ec.fieldContext_Fingerprint_reports(ctx, field)
|
||||
case "created":
|
||||
return ec.fieldContext_Fingerprint_created(ctx, field)
|
||||
case "updated":
|
||||
return ec.fieldContext_Fingerprint_updated(ctx, field)
|
||||
case "user_submitted":
|
||||
return ec.fieldContext_Fingerprint_user_submitted(ctx, field)
|
||||
case "user_reported":
|
||||
return ec.fieldContext_Fingerprint_user_reported(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Fingerprint", field.Name)
|
||||
},
|
||||
@@ -27746,12 +27878,16 @@ func (ec *executionContext) fieldContext_SceneEdit_fingerprints(_ context.Contex
|
||||
return ec.fieldContext_Fingerprint_duration(ctx, field)
|
||||
case "submissions":
|
||||
return ec.fieldContext_Fingerprint_submissions(ctx, field)
|
||||
case "reports":
|
||||
return ec.fieldContext_Fingerprint_reports(ctx, field)
|
||||
case "created":
|
||||
return ec.fieldContext_Fingerprint_created(ctx, field)
|
||||
case "updated":
|
||||
return ec.fieldContext_Fingerprint_updated(ctx, field)
|
||||
case "user_submitted":
|
||||
return ec.fieldContext_Fingerprint_user_submitted(ctx, field)
|
||||
case "user_reported":
|
||||
return ec.fieldContext_Fingerprint_user_reported(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type Fingerprint", field.Name)
|
||||
},
|
||||
@@ -34492,7 +34628,11 @@ func (ec *executionContext) unmarshalInputFingerprintSubmission(ctx context.Cont
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"scene_id", "fingerprint", "unmatch"}
|
||||
if _, present := asMap["vote"]; !present {
|
||||
asMap["vote"] = "VALID"
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"scene_id", "fingerprint", "unmatch", "vote"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
@@ -34520,6 +34660,13 @@ func (ec *executionContext) unmarshalInputFingerprintSubmission(ctx context.Cont
|
||||
return it, err
|
||||
}
|
||||
it.Unmatch = data
|
||||
case "vote":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("vote"))
|
||||
data, err := ec.unmarshalOFingerprintSubmissionType2ᚖgithubᚗcomᚋstashappᚋstashᚑboxᚋpkgᚋmodelsᚐFingerprintSubmissionType(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.Vote = data
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39492,6 +39639,11 @@ func (ec *executionContext) _Fingerprint(ctx context.Context, sel ast.SelectionS
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "reports":
|
||||
out.Values[i] = ec._Fingerprint_reports(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "created":
|
||||
out.Values[i] = ec._Fingerprint_created(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@@ -39507,6 +39659,11 @@ func (ec *executionContext) _Fingerprint(ctx context.Context, sel ast.SelectionS
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "user_reported":
|
||||
out.Values[i] = ec._Fingerprint_user_reported(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
@@ -49785,6 +49942,22 @@ func (ec *executionContext) unmarshalOFingerprintInput2ᚕᚖgithubᚗcomᚋstas
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOFingerprintSubmissionType2ᚖgithubᚗcomᚋstashappᚋstashᚑboxᚋpkgᚋmodelsᚐFingerprintSubmissionType(ctx context.Context, v interface{}) (*FingerprintSubmissionType, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(FingerprintSubmissionType)
|
||||
err := res.UnmarshalGQL(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOFingerprintSubmissionType2ᚖgithubᚗcomᚋstashappᚋstashᚑboxᚋpkgᚋmodelsᚐFingerprintSubmissionType(ctx context.Context, sel ast.SelectionSet, v *FingerprintSubmissionType) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOFuzzyDate2ᚖgithubᚗcomᚋstashappᚋstashᚑboxᚋpkgᚋmodelsᚐFuzzyDate(ctx context.Context, sel ast.SelectionSet, v *FuzzyDate) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
|
||||
@@ -137,13 +137,19 @@ type EyeColorCriterionInput struct {
|
||||
}
|
||||
|
||||
type Fingerprint struct {
|
||||
Hash string `json:"hash"`
|
||||
Algorithm FingerprintAlgorithm `json:"algorithm"`
|
||||
Duration int `json:"duration"`
|
||||
Submissions int `json:"submissions"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
UserSubmitted bool `json:"user_submitted"`
|
||||
Hash string `json:"hash"`
|
||||
Algorithm FingerprintAlgorithm `json:"algorithm"`
|
||||
Duration int `json:"duration"`
|
||||
// number of times this fingerprint has been submitted (excluding reports)
|
||||
Submissions int `json:"submissions"`
|
||||
// number of times this fingerprint has been reported
|
||||
Reports int `json:"reports"`
|
||||
Created time.Time `json:"created"`
|
||||
Updated time.Time `json:"updated"`
|
||||
// true if the current user submitted this fingerprint
|
||||
UserSubmitted bool `json:"user_submitted"`
|
||||
// true if the current user reported this fingerprint
|
||||
UserReported bool `json:"user_reported"`
|
||||
}
|
||||
|
||||
type FingerprintEditInput struct {
|
||||
@@ -170,9 +176,10 @@ type FingerprintQueryInput struct {
|
||||
}
|
||||
|
||||
type FingerprintSubmission struct {
|
||||
SceneID uuid.UUID `json:"scene_id"`
|
||||
Fingerprint *FingerprintInput `json:"fingerprint"`
|
||||
Unmatch *bool `json:"unmatch,omitempty"`
|
||||
SceneID uuid.UUID `json:"scene_id"`
|
||||
Fingerprint *FingerprintInput `json:"fingerprint"`
|
||||
Unmatch *bool `json:"unmatch,omitempty"`
|
||||
Vote *FingerprintSubmissionType `json:"vote,omitempty"`
|
||||
}
|
||||
|
||||
type FuzzyDate struct {
|
||||
@@ -1251,6 +1258,52 @@ func (e FingerprintAlgorithm) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type FingerprintSubmissionType string
|
||||
|
||||
const (
|
||||
// Positive vote
|
||||
FingerprintSubmissionTypeValid FingerprintSubmissionType = "VALID"
|
||||
// Report as invalid
|
||||
FingerprintSubmissionTypeInvalid FingerprintSubmissionType = "INVALID"
|
||||
// Remove vote
|
||||
FingerprintSubmissionTypeRemove FingerprintSubmissionType = "REMOVE"
|
||||
)
|
||||
|
||||
var AllFingerprintSubmissionType = []FingerprintSubmissionType{
|
||||
FingerprintSubmissionTypeValid,
|
||||
FingerprintSubmissionTypeInvalid,
|
||||
FingerprintSubmissionTypeRemove,
|
||||
}
|
||||
|
||||
func (e FingerprintSubmissionType) IsValid() bool {
|
||||
switch e {
|
||||
case FingerprintSubmissionTypeValid, FingerprintSubmissionTypeInvalid, FingerprintSubmissionTypeRemove:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (e FingerprintSubmissionType) String() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
func (e *FingerprintSubmissionType) UnmarshalGQL(v interface{}) error {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("enums must be strings")
|
||||
}
|
||||
|
||||
*e = FingerprintSubmissionType(str)
|
||||
if !e.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid FingerprintSubmissionType", str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e FingerprintSubmissionType) MarshalGQL(w io.Writer) {
|
||||
fmt.Fprint(w, strconv.Quote(e.String()))
|
||||
}
|
||||
|
||||
type GenderEnum string
|
||||
|
||||
const (
|
||||
|
||||
@@ -105,6 +105,7 @@ type SceneFingerprint struct {
|
||||
Algorithm string `db:"algorithm" json:"algorithm"`
|
||||
Duration int `db:"duration" json:"duration"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Vote int `db:"vote" json:"vote"`
|
||||
}
|
||||
|
||||
type SceneFingerprints []*SceneFingerprint
|
||||
@@ -125,32 +126,6 @@ func (f *SceneFingerprints) Add(o interface{}) {
|
||||
*f = append(*f, o.(*SceneFingerprint))
|
||||
}
|
||||
|
||||
type DBSceneFingerprint struct {
|
||||
SceneID uuid.UUID `db:"scene_id" json:"scene_id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
FingerprintID int `db:"fingerprint_id" json:"fingerprint_id"`
|
||||
Duration int `db:"duration" json:"duration"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
}
|
||||
|
||||
type DBSceneFingerprints []*DBSceneFingerprint
|
||||
|
||||
func (f DBSceneFingerprints) Each(fn func(interface{})) {
|
||||
for _, v := range f {
|
||||
fn(*v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f DBSceneFingerprints) EachPtr(fn func(interface{})) {
|
||||
for _, v := range f {
|
||||
fn(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *DBSceneFingerprints) Add(o interface{}) {
|
||||
*f = append(*f, o.(*DBSceneFingerprint))
|
||||
}
|
||||
|
||||
func CreateSceneFingerprints(sceneID uuid.UUID, fingerprints []*FingerprintEditInput) SceneFingerprints {
|
||||
var ret SceneFingerprints
|
||||
|
||||
@@ -172,7 +147,7 @@ func CreateSceneFingerprints(sceneID uuid.UUID, fingerprints []*FingerprintEditI
|
||||
return ret
|
||||
}
|
||||
|
||||
func CreateSubmittedSceneFingerprints(sceneID uuid.UUID, fingerprints []*FingerprintInput) SceneFingerprints {
|
||||
func CreateSubmittedSceneFingerprints(sceneID uuid.UUID, fingerprints []*FingerprintInput, vote int) SceneFingerprints {
|
||||
var ret SceneFingerprints
|
||||
|
||||
for _, fingerprint := range fingerprints {
|
||||
@@ -184,6 +159,7 @@ func CreateSubmittedSceneFingerprints(sceneID uuid.UUID, fingerprints []*Fingerp
|
||||
Hash: fingerprint.Hash,
|
||||
Algorithm: fingerprint.Algorithm.String(),
|
||||
Duration: fingerprint.Duration,
|
||||
Vote: vote,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ type SceneRepo interface {
|
||||
SoftDelete(scene Scene) (*Scene, error)
|
||||
CreateURLs(newJoins SceneURLs) error
|
||||
UpdateURLs(scene uuid.UUID, updatedJoins SceneURLs) error
|
||||
CreateFingerprints(newJoins SceneFingerprints) error
|
||||
CreateOrReplaceFingerprints(newJoins SceneFingerprints) error
|
||||
UpdateFingerprints(sceneID uuid.UUID, updatedJoins SceneFingerprints) error
|
||||
DestroyFingerprints(sceneID uuid.UUID, toDelete SceneFingerprints) error
|
||||
Find(id uuid.UUID) (*Scene, error)
|
||||
@@ -27,6 +27,7 @@ type SceneRepo interface {
|
||||
// GetAllFingerprints returns fingerprints for each of the scene ids provided.
|
||||
// currentUserID is used to populate the UserSubmitted field.
|
||||
GetAllFingerprints(currentUserID uuid.UUID, ids []uuid.UUID, onlySubmitted bool) ([][]*Fingerprint, []error)
|
||||
SubmittedHashExists(sceneID uuid.UUID, hash string, algorithm FingerprintAlgorithm) (bool, error)
|
||||
GetPerformers(id uuid.UUID) (PerformersScenes, error)
|
||||
GetAllAppearances(ids []uuid.UUID) ([]PerformersScenes, []error)
|
||||
GetURLs(id uuid.UUID) ([]*URL, error)
|
||||
|
||||
@@ -48,7 +48,7 @@ func Create(ctx context.Context, fac models.Repo, input models.SceneCreateInput)
|
||||
}
|
||||
|
||||
sceneFingerprints := models.CreateSceneFingerprints(scene.ID, input.Fingerprints)
|
||||
if err := qb.CreateFingerprints(sceneFingerprints); err != nil {
|
||||
if err := qb.CreateOrReplaceFingerprints(sceneFingerprints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -232,6 +232,17 @@ func Destroy(fac models.Repo, input models.SceneDestroyInput) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func submissionTypeToInt(t models.FingerprintSubmissionType) int {
|
||||
switch t {
|
||||
case models.FingerprintSubmissionTypeValid:
|
||||
return 1
|
||||
case models.FingerprintSubmissionTypeInvalid:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.FingerprintSubmission) (bool, error) {
|
||||
qb := fac.Scene()
|
||||
|
||||
@@ -257,11 +268,34 @@ func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.Finger
|
||||
input.Fingerprint.UserIds = []uuid.UUID{currentUserID}
|
||||
}
|
||||
|
||||
sceneFingerprint := models.CreateSubmittedSceneFingerprints(scene.ID, []*models.FingerprintInput{input.Fingerprint})
|
||||
// set the default vote
|
||||
vote := models.FingerprintSubmissionTypeValid
|
||||
if input.Vote != nil {
|
||||
vote = *input.Vote
|
||||
}
|
||||
|
||||
if input.Unmatch == nil || !*input.Unmatch {
|
||||
// if the user is reporting a fingerprint, ensure that the fingerprint has at least one submission
|
||||
if vote == models.FingerprintSubmissionTypeInvalid {
|
||||
submissionExists, err := qb.SubmittedHashExists(input.SceneID, input.Fingerprint.Hash, input.Fingerprint.Algorithm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !submissionExists {
|
||||
return false, errors.New("fingerprint has no submissions")
|
||||
}
|
||||
}
|
||||
|
||||
voteInt := submissionTypeToInt(vote)
|
||||
sceneFingerprint := models.CreateSubmittedSceneFingerprints(scene.ID, []*models.FingerprintInput{input.Fingerprint}, voteInt)
|
||||
|
||||
// vote == 0 means the user is unmatching the fingerprint
|
||||
// Unmatch is the deprecated field, but we still need to support it
|
||||
unmatch := vote == models.FingerprintSubmissionTypeRemove || (input.Unmatch != nil && *input.Unmatch)
|
||||
|
||||
if !unmatch {
|
||||
// set the new fingerprints
|
||||
if err := qb.CreateFingerprints(sceneFingerprint); err != nil {
|
||||
if err := qb.CreateOrReplaceFingerprints(sceneFingerprint); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
|
||||
34
pkg/sqlx/fingerprints.go
Normal file
34
pkg/sqlx/fingerprints.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package sqlx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
type dbSceneFingerprint struct {
|
||||
SceneID uuid.UUID `db:"scene_id" json:"scene_id"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
FingerprintID int `db:"fingerprint_id" json:"fingerprint_id"`
|
||||
Duration int `db:"duration" json:"duration"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
Vote int `db:"vote" json:"vote"`
|
||||
}
|
||||
|
||||
type dbSceneFingerprints []*dbSceneFingerprint
|
||||
|
||||
func (f dbSceneFingerprints) Each(fn func(interface{})) {
|
||||
for _, v := range f {
|
||||
fn(*v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f dbSceneFingerprints) EachPtr(fn func(interface{})) {
|
||||
for _, v := range f {
|
||||
fn(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *dbSceneFingerprints) Add(o interface{}) {
|
||||
*f = append(*f, o.(*dbSceneFingerprint))
|
||||
}
|
||||
@@ -27,7 +27,7 @@ var (
|
||||
})
|
||||
|
||||
sceneFingerprintTable = newTableJoin(sceneTable, "scene_fingerprints", sceneJoinKey, func() interface{} {
|
||||
return &models.DBSceneFingerprint{}
|
||||
return &dbSceneFingerprint{}
|
||||
})
|
||||
|
||||
sceneURLTable = newTableJoin(sceneTable, "scene_urls", sceneJoinKey, func() interface{} {
|
||||
@@ -79,21 +79,27 @@ func (qb *sceneQueryBuilder) UpdateURLs(scene uuid.UUID, updatedJoins models.Sce
|
||||
return qb.dbi.ReplaceJoins(sceneURLTable, scene, &updatedJoins)
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) CreateFingerprints(sceneFingerprints models.SceneFingerprints) error {
|
||||
conflictHandling := `ON CONFLICT DO NOTHING`
|
||||
func (qb *sceneQueryBuilder) CreateOrReplaceFingerprints(sceneFingerprints models.SceneFingerprints) error {
|
||||
conflictHandling := `
|
||||
ON CONFLICT ON CONSTRAINT scene_fingerprints_scene_id_fingerprint_id_user_id_key
|
||||
DO UPDATE SET
|
||||
duration = EXCLUDED.duration,
|
||||
vote = EXCLUDED.vote
|
||||
`
|
||||
|
||||
var fingerprints models.DBSceneFingerprints
|
||||
var fingerprints dbSceneFingerprints
|
||||
for _, fp := range sceneFingerprints {
|
||||
id, err := qb.getOrCreateFingerprintID(fp.Hash, fp.Algorithm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fingerprints = append(fingerprints, &models.DBSceneFingerprint{
|
||||
fingerprints = append(fingerprints, &dbSceneFingerprint{
|
||||
FingerprintID: id,
|
||||
SceneID: fp.SceneID,
|
||||
UserID: fp.UserID,
|
||||
Duration: fp.Duration,
|
||||
Vote: fp.Vote,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -105,7 +111,7 @@ func (qb *sceneQueryBuilder) UpdateFingerprints(sceneID uuid.UUID, updatedJoins
|
||||
return err
|
||||
}
|
||||
|
||||
return qb.CreateFingerprints(updatedJoins)
|
||||
return qb.CreateOrReplaceFingerprints(updatedJoins)
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) DestroyFingerprints(sceneID uuid.UUID, toDestroy models.SceneFingerprints) error {
|
||||
@@ -572,14 +578,17 @@ func (qb *sceneQueryBuilder) queryScenes(query string, args []interface{}) (mode
|
||||
}
|
||||
|
||||
type sceneFingerprintGroup struct {
|
||||
SceneID uuid.UUID `db:"scene_id"`
|
||||
Hash string `db:"hash"`
|
||||
Algorithm models.FingerprintAlgorithm `db:"algorithm"`
|
||||
Duration float64 `db:"duration"`
|
||||
Submissions int `db:"submissions"`
|
||||
UserSubmitted bool `db:"user_submitted"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
SceneID uuid.UUID `db:"scene_id"`
|
||||
Hash string `db:"hash"`
|
||||
Algorithm models.FingerprintAlgorithm `db:"algorithm"`
|
||||
Duration float64 `db:"duration"`
|
||||
Submissions int `db:"submissions"`
|
||||
Reports int `db:"reports"`
|
||||
NetSubmissions int `db:"net_submissions"`
|
||||
UserSubmitted bool `db:"user_submitted"`
|
||||
UserReported bool `db:"user_reported"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
func fingerprintGroupToFingerprint(fpg sceneFingerprintGroup) *models.Fingerprint {
|
||||
@@ -588,7 +597,9 @@ func fingerprintGroupToFingerprint(fpg sceneFingerprintGroup) *models.Fingerprin
|
||||
Algorithm: fpg.Algorithm,
|
||||
Duration: int(fpg.Duration),
|
||||
Submissions: fpg.Submissions,
|
||||
Reports: fpg.Reports,
|
||||
UserSubmitted: fpg.UserSubmitted,
|
||||
UserReported: fpg.UserReported,
|
||||
Created: fpg.CreatedAt,
|
||||
Updated: fpg.UpdatedAt,
|
||||
}
|
||||
@@ -612,10 +623,13 @@ func (qb *sceneQueryBuilder) GetAllFingerprints(currentUserID uuid.UUID, ids []u
|
||||
FP.hash,
|
||||
FP.algorithm,
|
||||
mode() WITHIN GROUP (ORDER BY SFP.duration) as duration,
|
||||
COUNT(SFP.fingerprint_id) as submissions,
|
||||
COUNT(CASE WHEN SFP.vote = 1 THEN 1 END) as submissions,
|
||||
COUNT(CASE WHEN SFP.vote = -1 THEN 1 END) as reports,
|
||||
SUM(SFP.vote) as net_submissions,
|
||||
MIN(created_at) as created_at,
|
||||
MAX(created_at) as updated_at,
|
||||
bool_or(SFP.user_id = :userid) as user_submitted
|
||||
bool_or(SFP.user_id = :userid AND SFP.vote = 1) as user_submitted,
|
||||
bool_or(SFP.user_id = :userid AND SFP.vote = -1) as user_reported
|
||||
FROM scene_fingerprints SFP
|
||||
JOIN fingerprints FP ON SFP.fingerprint_id = FP.id
|
||||
WHERE SFP.scene_id IN (:sceneids)
|
||||
@@ -627,7 +641,7 @@ func (qb *sceneQueryBuilder) GetAllFingerprints(currentUserID uuid.UUID, ids []u
|
||||
|
||||
query += `
|
||||
GROUP BY SFP.scene_id, FP.algorithm, FP.hash
|
||||
ORDER BY submissions DESC`
|
||||
ORDER BY net_submissions DESC`
|
||||
|
||||
arg := map[string]interface{}{
|
||||
"userid": currentUserID,
|
||||
@@ -667,6 +681,38 @@ func (qb *sceneQueryBuilder) GetAllFingerprints(currentUserID uuid.UUID, ids []u
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SubmittedHashExists returns true if the given hash exists for the given scene
|
||||
func (qb *sceneQueryBuilder) SubmittedHashExists(sceneID uuid.UUID, hash string, algorithm models.FingerprintAlgorithm) (bool, error) {
|
||||
query := `
|
||||
SELECT
|
||||
1
|
||||
FROM scene_fingerprints f
|
||||
JOIN fingerprints fp ON f.fingerprint_id = fp.id
|
||||
WHERE f.scene_id = :sceneid AND fp.hash = :hash AND fp.algorithm = :algorithm AND f.vote = 1
|
||||
`
|
||||
|
||||
arg := map[string]interface{}{
|
||||
"sceneid": sceneID,
|
||||
"hash": hash,
|
||||
"algorithm": algorithm,
|
||||
}
|
||||
|
||||
query, args, err := sqlx.Named(query, arg)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
result := false
|
||||
if err := qb.dbi.queryFunc(query, args, func(rows *sqlx.Rows) error {
|
||||
result = true
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) GetPerformers(id uuid.UUID) (models.PerformersScenes, error) {
|
||||
joins := models.PerformersScenes{}
|
||||
err := qb.dbi.FindJoins(scenePerformerTable, id, &joins)
|
||||
@@ -1005,13 +1051,14 @@ func (qb *sceneQueryBuilder) addFingerprintsFromEdit(scene *models.Scene, data *
|
||||
Algorithm: fingerprint.Algorithm.String(),
|
||||
SceneID: scene.ID,
|
||||
UserID: userID,
|
||||
Vote: 1,
|
||||
Duration: fingerprint.Duration,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return qb.CreateFingerprints(newFingerprints)
|
||||
return qb.CreateOrReplaceFingerprints(newFingerprints)
|
||||
}
|
||||
|
||||
func (qb *sceneQueryBuilder) getOrCreateFingerprintID(hash string, algorithm string) (int, error) {
|
||||
|
||||
Reference in New Issue
Block a user