From 7f62360693f06b1f5ade2e2fd55c1f29477cdbb7 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:12:49 +1000 Subject: [PATCH 1/9] Add interface to flag fingerprints --- frontend/src/graphql/types.ts | 3 ++ graphql/schema/types/scene.graphql | 4 ++- pkg/database/database.go | 2 +- .../postgres/37_fingerprint_vote.up.sql | 2 ++ pkg/models/generated_exec.go | 13 +++++-- pkg/models/generated_models.go | 2 ++ pkg/models/model_scene.go | 30 ++-------------- pkg/models/scene.go | 2 +- pkg/scene/scene.go | 17 +++++++--- pkg/sqlx/fingerprints.go | 34 +++++++++++++++++++ pkg/sqlx/querybuilder_scene.go | 20 +++++++---- 11 files changed, 86 insertions(+), 43 deletions(-) create mode 100644 pkg/database/migrations/postgres/37_fingerprint_vote.up.sql create mode 100644 pkg/sqlx/fingerprints.go diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index f3b57789b..e79a914c3 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -322,7 +322,10 @@ export type FingerprintQueryInput = { export type FingerprintSubmission = { fingerprint: FingerprintInput; scene_id: Scalars["ID"]; + /** @deprecated Use `vote` with 0 instead */ unmatch?: InputMaybe; + /** positive for default behaviour, negative to report as invalid, zero to remove vote */ + vote?: InputMaybe; }; export type FuzzyDate = { diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index a15ba60c9..546414f99 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -64,7 +64,9 @@ input FingerprintQueryInput { input FingerprintSubmission { scene_id: ID! fingerprint: FingerprintInput! - unmatch: Boolean + unmatch: Boolean @deprecated(reason: "Use `vote` with 0 instead") + "positive for default behaviour, negative to report as invalid, zero to remove vote" + vote: Int } type Scene { diff --git a/pkg/database/database.go b/pkg/database/database.go index b8144804d..824f76784 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -4,7 +4,7 @@ import ( "github.com/jmoiron/sqlx" ) -var appSchemaVersion uint = 36 +var appSchemaVersion uint = 37 var databaseProviders map[string]databaseProvider diff --git a/pkg/database/migrations/postgres/37_fingerprint_vote.up.sql b/pkg/database/migrations/postgres/37_fingerprint_vote.up.sql new file mode 100644 index 000000000..cf67bad51 --- /dev/null +++ b/pkg/database/migrations/postgres/37_fingerprint_vote.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE "scene_fingerprints" + ADD COLUMN "vote" SMALLINT NOT NULL DEFAULT 1 CHECK (vote = -1 OR vote = 1); diff --git a/pkg/models/generated_exec.go b/pkg/models/generated_exec.go index ddabfae1e..62226277a 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -4961,7 +4961,9 @@ input FingerprintQueryInput { input FingerprintSubmission { scene_id: ID! fingerprint: FingerprintInput! - unmatch: Boolean + unmatch: Boolean @deprecated(reason: "Use ` + "`" + `vote` + "`" + ` with 0 instead") + "positive for default behaviour, negative to report as invalid, zero to remove vote" + vote: Int } type Scene { @@ -31927,7 +31929,7 @@ func (ec *executionContext) unmarshalInputFingerprintSubmission(ctx context.Cont asMap[k] = v } - fieldsInOrder := [...]string{"scene_id", "fingerprint", "unmatch"} + fieldsInOrder := [...]string{"scene_id", "fingerprint", "unmatch", "vote"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -31955,6 +31957,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.unmarshalOInt2ᚖint(ctx, v) + if err != nil { + return it, err + } + it.Vote = data } } diff --git a/pkg/models/generated_models.go b/pkg/models/generated_models.go index 9735a9c89..3337567de 100644 --- a/pkg/models/generated_models.go +++ b/pkg/models/generated_models.go @@ -174,6 +174,8 @@ type FingerprintSubmission struct { SceneID uuid.UUID `json:"scene_id"` Fingerprint *FingerprintInput `json:"fingerprint"` Unmatch *bool `json:"unmatch,omitempty"` + // positive for default behaviour, negative to report as invalid, zero to remove vote + Vote *int `json:"vote,omitempty"` } type FuzzyDate struct { diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index 1467172ca..b050c3c4c 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -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, }) } } diff --git a/pkg/models/scene.go b/pkg/models/scene.go index fd32be046..0231d9ced 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -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) diff --git a/pkg/scene/scene.go b/pkg/scene/scene.go index 040dabe36..b1f521ffd 100644 --- a/pkg/scene/scene.go +++ b/pkg/scene/scene.go @@ -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 } @@ -257,11 +257,20 @@ 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}) + vote := 1 + if input.Vote != nil { + vote = *input.Vote + } + + sceneFingerprint := models.CreateSubmittedSceneFingerprints(scene.ID, []*models.FingerprintInput{input.Fingerprint}, vote) + + // vote == 0 means the user is unmatching the fingerprint + // Unmatch is the deprecated field, but we still need to support it + unmatch := vote == 0 || (input.Unmatch != nil && *input.Unmatch) - if 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 { diff --git a/pkg/sqlx/fingerprints.go b/pkg/sqlx/fingerprints.go new file mode 100644 index 000000000..149a6aafd --- /dev/null +++ b/pkg/sqlx/fingerprints.go @@ -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)) +} diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index fe239a5bb..7009f4c1c 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -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_hash_unique + 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, + // TODO: 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 { @@ -985,7 +991,7 @@ func (qb *sceneQueryBuilder) addFingerprintsFromEdit(scene *models.Scene, data * } } - return qb.CreateFingerprints(newFingerprints) + return qb.CreateOrReplaceFingerprints(newFingerprints) } func (qb *sceneQueryBuilder) getOrCreateFingerprintID(hash string, algorithm string) (int, error) { From 4968cc6befa2bc09c266ae11d6cf39f8b4c4d076 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 11:41:20 +1000 Subject: [PATCH 2/9] Add field for reports --- frontend/src/graphql/types.ts | 3 ++ graphql/schema/types/scene.graphql | 3 ++ pkg/models/generated_exec.go | 68 ++++++++++++++++++++++++++++++ pkg/models/generated_models.go | 17 +++++--- pkg/sqlx/querybuilder_scene.go | 5 ++- 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index e79a914c3..eae748fdd 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -283,6 +283,9 @@ export type Fingerprint = { created: Scalars["Time"]; duration: Scalars["Int"]; hash: Scalars["String"]; + /** number of times this fingerprint has been reported */ + reports: Scalars["Int"]; + /** number of times this fingerprint has been submitted (excluding reports) */ submissions: Scalars["Int"]; updated: Scalars["Time"]; user_submitted: Scalars["Boolean"]; diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 546414f99..bdf5e36dd 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -26,7 +26,10 @@ 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! user_submitted: Boolean! diff --git a/pkg/models/generated_exec.go b/pkg/models/generated_exec.go index 62226277a..c972e04b3 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -140,6 +140,7 @@ 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 UserSubmitted func(childComplexity int) int @@ -1217,6 +1218,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 @@ -4923,7 +4931,10 @@ 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! user_submitted: Boolean! @@ -9040,6 +9051,50 @@ func (ec *executionContext) fieldContext_Fingerprint_submissions(ctx context.Con 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(ctx 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 { @@ -23158,6 +23213,8 @@ 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": @@ -24695,6 +24752,8 @@ func (ec *executionContext) fieldContext_SceneEdit_added_fingerprints(ctx 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": @@ -24752,6 +24811,8 @@ func (ec *executionContext) fieldContext_SceneEdit_removed_fingerprints(ctx cont 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": @@ -25196,6 +25257,8 @@ func (ec *executionContext) fieldContext_SceneEdit_fingerprints(ctx context.Cont 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": @@ -36854,6 +36917,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 { diff --git a/pkg/models/generated_models.go b/pkg/models/generated_models.go index 3337567de..4145a5a9a 100644 --- a/pkg/models/generated_models.go +++ b/pkg/models/generated_models.go @@ -138,13 +138,16 @@ 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"` + UserSubmitted bool `json:"user_submitted"` } type FingerprintEditInput struct { diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index 7009f4c1c..a97d7f7e1 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -557,6 +557,7 @@ type sceneFingerprintGroup struct { Algorithm models.FingerprintAlgorithm `db:"algorithm"` Duration float64 `db:"duration"` Submissions int `db:"submissions"` + Reports int `db:"reports"` UserSubmitted bool `db:"user_submitted"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` @@ -568,6 +569,7 @@ func fingerprintGroupToFingerprint(fpg sceneFingerprintGroup) *models.Fingerprin Algorithm: fpg.Algorithm, Duration: int(fpg.Duration), Submissions: fpg.Submissions, + Reports: fpg.Reports, UserSubmitted: fpg.UserSubmitted, Created: fpg.CreatedAt, Updated: fpg.UpdatedAt, @@ -592,7 +594,8 @@ 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, MIN(created_at) as created_at, MAX(created_at) as updated_at, bool_or(SFP.user_id = :userid) as user_submitted From 978f6beeb75d5ebfe5529c65e49447b7524df4a2 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 12:55:08 +1000 Subject: [PATCH 3/9] Show reports in UI --- .../src/graphql/fragments/SceneFragment.gql | 1 + .../graphql/mutations/UnmatchFingerprint.gql | 2 +- frontend/src/graphql/types.ts | 49 +++++++ frontend/src/pages/scenes/Scene.tsx | 131 ++++++++++-------- frontend/src/pages/scenes/styles.scss | 14 ++ 5 files changed, 139 insertions(+), 58 deletions(-) diff --git a/frontend/src/graphql/fragments/SceneFragment.gql b/frontend/src/graphql/fragments/SceneFragment.gql index f4ad192f3..fd3c16d03 100644 --- a/frontend/src/graphql/fragments/SceneFragment.gql +++ b/frontend/src/graphql/fragments/SceneFragment.gql @@ -35,6 +35,7 @@ fragment SceneFragment on Scene { algorithm duration submissions + reports user_submitted created updated diff --git a/frontend/src/graphql/mutations/UnmatchFingerprint.gql b/frontend/src/graphql/mutations/UnmatchFingerprint.gql index 25a2c12f2..b4eecdfbf 100644 --- a/frontend/src/graphql/mutations/UnmatchFingerprint.gql +++ b/frontend/src/graphql/mutations/UnmatchFingerprint.gql @@ -6,7 +6,7 @@ mutation UnmatchFingerprint( ) { unmatchFingerprint: submitFingerprint( input: { - unmatch: true + vote: 0 scene_id: $scene_id fingerprint: { hash: $hash, algorithm: $algorithm, duration: $duration } } diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index eae748fdd..daaed3a11 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -2030,6 +2030,7 @@ export type EditFragment = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -2801,6 +2802,7 @@ export type EditFragment = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -3002,6 +3004,7 @@ export type SceneFragment = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -3363,6 +3366,7 @@ export type ApplyEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -4189,6 +4193,7 @@ export type ApplyEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -4573,6 +4578,7 @@ export type PerformerEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -5399,6 +5405,7 @@ export type PerformerEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -5602,6 +5609,7 @@ export type PerformerEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -6428,6 +6436,7 @@ export type PerformerEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -6666,6 +6675,7 @@ export type SceneEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -7492,6 +7502,7 @@ export type SceneEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -7695,6 +7706,7 @@ export type SceneEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -8521,6 +8533,7 @@ export type SceneEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -8723,6 +8736,7 @@ export type StudioEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -9549,6 +9563,7 @@ export type StudioEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -9752,6 +9767,7 @@ export type StudioEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -10578,6 +10594,7 @@ export type StudioEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -10780,6 +10797,7 @@ export type TagEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -11606,6 +11624,7 @@ export type TagEditMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -11809,6 +11828,7 @@ export type TagEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -12635,6 +12655,7 @@ export type TagEditUpdateMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -12971,6 +12992,7 @@ export type VoteMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -13797,6 +13819,7 @@ export type VoteMutation = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -14258,6 +14281,7 @@ export type EditQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -15084,6 +15108,7 @@ export type EditQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -15280,6 +15305,7 @@ export type EditUpdateQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -15727,6 +15753,7 @@ export type EditsQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -16573,6 +16600,7 @@ export type EditsQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -16922,6 +16950,7 @@ export type QueryExistingSceneQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -17070,6 +17099,7 @@ export type QueryExistingSceneQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -17916,6 +17946,7 @@ export type QueryExistingSceneQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -18034,6 +18065,7 @@ export type SceneQuery = { algorithm: FingerprintAlgorithm; duration: number; submissions: number; + reports: number; user_submitted: boolean; created: string; updated: string; @@ -19063,6 +19095,7 @@ 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" }, @@ -20670,6 +20703,7 @@ 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" }, @@ -21890,6 +21924,7 @@ 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" }, @@ -24325,6 +24360,7 @@ 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" }, @@ -25933,6 +25969,7 @@ 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" }, @@ -27705,6 +27742,7 @@ 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" }, @@ -29310,6 +29348,7 @@ 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" }, @@ -30902,6 +30941,7 @@ 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" }, @@ -32507,6 +32547,7 @@ 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" }, @@ -34099,6 +34140,7 @@ 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" }, @@ -35704,6 +35746,7 @@ 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" }, @@ -37905,6 +37948,7 @@ 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" }, @@ -40348,6 +40392,7 @@ 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" }, @@ -42511,6 +42556,7 @@ 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" }, @@ -42980,6 +43026,7 @@ 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" }, @@ -45450,6 +45497,7 @@ 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" }, @@ -46833,6 +46881,7 @@ 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" }, diff --git a/frontend/src/pages/scenes/Scene.tsx b/frontend/src/pages/scenes/Scene.tsx index 544b526d1..dfde3e55e 100644 --- a/frontend/src/pages/scenes/Scene.tsx +++ b/frontend/src/pages/scenes/Scene.tsx @@ -1,10 +1,11 @@ -import { FC, useContext } from "react"; +import { FC, useContext, useMemo } from "react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { Button, Card, Tabs, Tab, Table } from "react-bootstrap"; import { faCheckCircle, faTimesCircle, faSpinner, + faTriangleExclamation, } from "@fortawesome/free-solid-svg-icons"; import { @@ -83,70 +84,86 @@ const SceneComponent: FC = ({ scene }) => { }) .map((p, index) => (index % 2 === 2 ? [" • ", p] : p)); - async function handleFingerprintUnmatch(fingerprint: Fingerprint) { - if (unmatching) return; + const fingerprints = useMemo(() => { + 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`, - }); - } + 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 ( + handleFingerprintUnmatch(fingerprint)} + > + {!unmatching ? ( + <> + + + + ) : ( + + )} + + ); + } + } - function maybeRenderSubmitted(fingerprint: Fingerprint) { - if (fingerprint.user_submitted) { + function renderSubmissions(fingerprint: Fingerprint) { return ( - handleFingerprintUnmatch(fingerprint)} - > - {!unmatching ? ( - <> - - - - ) : ( - + + + {fingerprint.submissions} + + {fingerprint.reports > 0 && ( + + {fingerprint.reports} + )} + {maybeRenderSubmitted(fingerprint)} ); } - } - const fingerprints = scene.fingerprints.map((fingerprint) => ( - - {fingerprint.algorithm} - - - {fingerprint.hash} - - - - - {formatDuration(fingerprint.duration)} - - - - {fingerprint.submissions} - {maybeRenderSubmitted(fingerprint)} - - {formatDateTime(fingerprint.created)} - {formatDateTime(fingerprint.updated)} - - )); + return scene.fingerprints.map((fingerprint) => ( + + {fingerprint.algorithm} + + + {fingerprint.hash} + + + + + {formatDuration(fingerprint.duration)} + + + {renderSubmissions(fingerprint)} + {formatDateTime(fingerprint.created)} + {formatDateTime(fingerprint.updated)} + + )); + }, [scene.fingerprints, unmatchFingerprint, unmatching, addToast, scene.id]); + const tags = [...scene.tags].sort(compareByName).map((tag) => (
  • Date: Fri, 25 Aug 2023 13:01:57 +1000 Subject: [PATCH 4/9] Add user_reported fingerprint field --- frontend/src/graphql/types.ts | 7 ++- graphql/schema/types/scene.graphql | 3 ++ pkg/models/generated_exec.go | 68 ++++++++++++++++++++++++++++++ pkg/models/generated_models.go | 11 +++-- pkg/sqlx/querybuilder_scene.go | 5 ++- 5 files changed, 87 insertions(+), 7 deletions(-) diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index daaed3a11..3eb0f032f 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -288,6 +288,9 @@ export type Fingerprint = { /** number of times this fingerprint has been submitted (excluding reports) */ submissions: Scalars["Int"]; updated: Scalars["Time"]; + /** true if the current user reported this fingerprint */ + user_reported: Scalars["Boolean"]; + /** true if the current user submitted this fingerprint */ user_submitted: Scalars["Boolean"]; }; @@ -36995,8 +36998,8 @@ export const UnmatchFingerprintDocument = { fields: [ { kind: "ObjectField", - name: { kind: "Name", value: "unmatch" }, - value: { kind: "BooleanValue", value: true }, + name: { kind: "Name", value: "vote" }, + value: { kind: "IntValue", value: "0" }, }, { kind: "ObjectField", diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index bdf5e36dd..b0bdc3886 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -32,7 +32,10 @@ type Fingerprint { 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 { diff --git a/pkg/models/generated_exec.go b/pkg/models/generated_exec.go index c972e04b3..6892d1254 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -143,6 +143,7 @@ type ComplexityRoot struct { 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 } @@ -1239,6 +1240,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 @@ -4937,7 +4945,10 @@ type Fingerprint { 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 { @@ -9227,6 +9238,50 @@ func (ec *executionContext) fieldContext_Fingerprint_user_submitted(ctx context. 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(ctx 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 { @@ -23221,6 +23276,8 @@ func (ec *executionContext) fieldContext_Scene_fingerprints(ctx context.Context, 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) }, @@ -24760,6 +24817,8 @@ func (ec *executionContext) fieldContext_SceneEdit_added_fingerprints(ctx contex 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) }, @@ -24819,6 +24878,8 @@ func (ec *executionContext) fieldContext_SceneEdit_removed_fingerprints(ctx cont 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) }, @@ -25265,6 +25326,8 @@ func (ec *executionContext) fieldContext_SceneEdit_fingerprints(ctx context.Cont 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) }, @@ -36937,6 +37000,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)) } diff --git a/pkg/models/generated_models.go b/pkg/models/generated_models.go index 4145a5a9a..79c336831 100644 --- a/pkg/models/generated_models.go +++ b/pkg/models/generated_models.go @@ -144,10 +144,13 @@ type Fingerprint struct { // 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"` - UserSubmitted bool `json:"user_submitted"` + 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 { diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index a97d7f7e1..30dbfadfe 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -559,6 +559,7 @@ type sceneFingerprintGroup struct { Submissions int `db:"submissions"` Reports int `db:"reports"` UserSubmitted bool `db:"user_submitted"` + UserReported bool `db:"user_reported"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } @@ -571,6 +572,7 @@ func fingerprintGroupToFingerprint(fpg sceneFingerprintGroup) *models.Fingerprin Submissions: fpg.Submissions, Reports: fpg.Reports, UserSubmitted: fpg.UserSubmitted, + UserReported: fpg.UserReported, Created: fpg.CreatedAt, Updated: fpg.UpdatedAt, } @@ -598,7 +600,8 @@ func (qb *sceneQueryBuilder) GetAllFingerprints(currentUserID uuid.UUID, ids []u COUNT(CASE WHEN SFP.vote = -1 THEN 1 END) as reports, 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) From 9cd82957dd6ca28a49c6a4994b10981e6ecf96d2 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:06:41 +1000 Subject: [PATCH 5/9] Indicate user reported or submitted --- .../src/graphql/fragments/SceneFragment.gql | 1 + frontend/src/graphql/types.ts | 100 ++++++++++++++++++ frontend/src/pages/scenes/Scene.tsx | 10 +- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/frontend/src/graphql/fragments/SceneFragment.gql b/frontend/src/graphql/fragments/SceneFragment.gql index fd3c16d03..afd46c1aa 100644 --- a/frontend/src/graphql/fragments/SceneFragment.gql +++ b/frontend/src/graphql/fragments/SceneFragment.gql @@ -37,6 +37,7 @@ fragment SceneFragment on Scene { submissions reports user_submitted + user_reported created updated } diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index 3eb0f032f..dffe5744b 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -2035,6 +2035,7 @@ export type EditFragment = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -2807,6 +2808,7 @@ export type EditFragment = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -3009,6 +3011,7 @@ export type SceneFragment = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -3371,6 +3374,7 @@ export type ApplyEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -4198,6 +4202,7 @@ export type ApplyEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -4583,6 +4588,7 @@ export type PerformerEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -5410,6 +5416,7 @@ export type PerformerEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -5614,6 +5621,7 @@ export type PerformerEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -6441,6 +6449,7 @@ export type PerformerEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -6680,6 +6689,7 @@ export type SceneEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -7507,6 +7517,7 @@ export type SceneEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -7711,6 +7722,7 @@ export type SceneEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -8538,6 +8550,7 @@ export type SceneEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -8741,6 +8754,7 @@ export type StudioEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -9568,6 +9582,7 @@ export type StudioEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -9772,6 +9787,7 @@ export type StudioEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -10599,6 +10615,7 @@ export type StudioEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -10802,6 +10819,7 @@ export type TagEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -11629,6 +11647,7 @@ export type TagEditMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -11833,6 +11852,7 @@ export type TagEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -12660,6 +12680,7 @@ export type TagEditUpdateMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -12997,6 +13018,7 @@ export type VoteMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -13824,6 +13846,7 @@ export type VoteMutation = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -14286,6 +14309,7 @@ export type EditQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -15113,6 +15137,7 @@ export type EditQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -15310,6 +15335,7 @@ export type EditUpdateQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -15758,6 +15784,7 @@ export type EditsQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -16605,6 +16632,7 @@ export type EditsQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -16955,6 +16983,7 @@ export type QueryExistingSceneQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -17104,6 +17133,7 @@ export type QueryExistingSceneQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -17951,6 +17981,7 @@ export type QueryExistingSceneQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -18070,6 +18101,7 @@ export type SceneQuery = { submissions: number; reports: number; user_submitted: boolean; + user_reported: boolean; created: string; updated: string; }>; @@ -19103,6 +19135,10 @@ export const SceneFragmentDoc = { 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" } }, ], @@ -20711,6 +20747,10 @@ export const EditFragmentDoc = { 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" } }, ], @@ -21932,6 +21972,10 @@ export const ApplyEditDocument = { 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" } }, ], @@ -24368,6 +24412,10 @@ export const PerformerEditDocument = { 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" } }, ], @@ -25977,6 +26025,10 @@ export const PerformerEditUpdateDocument = { 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" } }, ], @@ -27750,6 +27802,10 @@ export const SceneEditDocument = { 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" } }, ], @@ -29356,6 +29412,10 @@ export const SceneEditUpdateDocument = { 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" } }, ], @@ -30949,6 +31009,10 @@ export const StudioEditDocument = { 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" } }, ], @@ -32555,6 +32619,10 @@ export const StudioEditUpdateDocument = { 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" } }, ], @@ -34148,6 +34216,10 @@ export const TagEditDocument = { 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" } }, ], @@ -35754,6 +35826,10 @@ export const TagEditUpdateDocument = { 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" } }, ], @@ -37956,6 +38032,10 @@ export const VoteDocument = { 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" } }, ], @@ -40400,6 +40480,10 @@ export const EditDocument = { 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" } }, ], @@ -42564,6 +42648,10 @@ export const EditUpdateDocument = { 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" } }, ], @@ -43034,6 +43122,10 @@ export const EditsDocument = { 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" } }, ], @@ -45505,6 +45597,10 @@ export const QueryExistingSceneDocument = { 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" } }, ], @@ -46889,6 +46985,10 @@ export const SceneDocument = { 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" } }, ], diff --git a/frontend/src/pages/scenes/Scene.tsx b/frontend/src/pages/scenes/Scene.tsx index dfde3e55e..ab7aad3e1 100644 --- a/frontend/src/pages/scenes/Scene.tsx +++ b/frontend/src/pages/scenes/Scene.tsx @@ -105,8 +105,11 @@ const SceneComponent: FC = ({ scene }) => { }); } - function maybeRenderSubmitted(fingerprint: Fingerprint) { - if (fingerprint.user_submitted) { + function maybeRenderSubmitted(fingerprint: Fingerprint, reported: boolean) { + if ( + (!reported && fingerprint.user_submitted) || + (reported && fingerprint.user_reported) + ) { return ( = ({ scene }) => { {fingerprint.reports > 0 && ( {fingerprint.reports} + {maybeRenderSubmitted(fingerprint, true)} )} - {maybeRenderSubmitted(fingerprint)} + {maybeRenderSubmitted(fingerprint, false)} ); } From 908e0f80b860fddeb402fd8ab9913df8f185df84 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:17:59 +1000 Subject: [PATCH 6/9] Order by submissions - reports --- pkg/sqlx/querybuilder_scene.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index 30dbfadfe..3a550b5f5 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -552,16 +552,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"` - Reports int `db:"reports"` - UserSubmitted bool `db:"user_submitted"` - UserReported bool `db:"user_reported"` - 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 { @@ -598,6 +599,7 @@ func (qb *sceneQueryBuilder) GetAllFingerprints(currentUserID uuid.UUID, ids []u mode() WITHIN GROUP (ORDER BY SFP.duration) as duration, 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 AND SFP.vote = 1) as user_submitted, @@ -613,7 +615,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, From 0320782079c14c4afac079f2005f4bc6718e99e4 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Fri, 25 Aug 2023 13:39:32 +1000 Subject: [PATCH 7/9] Validate vote field --- pkg/models/scene.go | 1 + pkg/scene/scene.go | 27 ++++++++++++++++++++++++++- pkg/sqlx/querybuilder_scene.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/pkg/models/scene.go b/pkg/models/scene.go index 0231d9ced..f370221fe 100644 --- a/pkg/models/scene.go +++ b/pkg/models/scene.go @@ -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) diff --git a/pkg/scene/scene.go b/pkg/scene/scene.go index b1f521ffd..92dc38dc4 100644 --- a/pkg/scene/scene.go +++ b/pkg/scene/scene.go @@ -232,6 +232,19 @@ func Destroy(fac models.Repo, input models.SceneDestroyInput) (bool, error) { return true, nil } +// clampVote ensures that vote is between -1 and 1 +func clampVote(vote int) int { + if vote > 1 { + return 1 + } + + if vote < -1 { + return -1 + } + + return vote +} + func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.FingerprintSubmission) (bool, error) { qb := fac.Scene() @@ -259,7 +272,19 @@ func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.Finger vote := 1 if input.Vote != nil { - vote = *input.Vote + vote = clampVote(*input.Vote) + } + + // if the user is reporting a fingerprint, ensure that the fingerprint has at least one submission + if vote == -1 { + 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") + } } sceneFingerprint := models.CreateSubmittedSceneFingerprints(scene.ID, []*models.FingerprintInput{input.Fingerprint}, vote) diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index 3a550b5f5..3ef7e74d0 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -655,6 +655,37 @@ 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 + WHERE f.scene_id = :sceneid AND f.hash = :hash AND f.algorithm = :algorithm AND 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) From 3edc2330fff58aa70a96dcbc775e8a5295264703 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Sun, 25 Aug 2024 15:27:10 +1000 Subject: [PATCH 8/9] Change vote parameter to enum --- .../graphql/mutations/UnmatchFingerprint.gql | 2 +- frontend/src/graphql/types.ts | 16 ++++-- graphql/schema/types/scene.graphql | 14 ++++- pkg/models/generated_exec.go | 36 ++++++++++-- pkg/models/generated_models.go | 55 +++++++++++++++++-- pkg/scene/scene.go | 26 ++++----- pkg/sqlx/fingerprints.go | 8 +-- pkg/sqlx/querybuilder_scene.go | 10 ++-- 8 files changed, 129 insertions(+), 38 deletions(-) diff --git a/frontend/src/graphql/mutations/UnmatchFingerprint.gql b/frontend/src/graphql/mutations/UnmatchFingerprint.gql index b4eecdfbf..39c90f04b 100644 --- a/frontend/src/graphql/mutations/UnmatchFingerprint.gql +++ b/frontend/src/graphql/mutations/UnmatchFingerprint.gql @@ -6,7 +6,7 @@ mutation UnmatchFingerprint( ) { unmatchFingerprint: submitFingerprint( input: { - vote: 0 + vote: REMOVE scene_id: $scene_id fingerprint: { hash: $hash, algorithm: $algorithm, duration: $duration } } diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index dffe5744b..68e4329b2 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -328,12 +328,20 @@ export type FingerprintQueryInput = { export type FingerprintSubmission = { fingerprint: FingerprintInput; scene_id: Scalars["ID"]; - /** @deprecated Use `vote` with 0 instead */ + /** @deprecated Use `vote` with REMOVE instead */ unmatch?: InputMaybe; - /** positive for default behaviour, negative to report as invalid, zero to remove vote */ - vote?: InputMaybe; + vote?: InputMaybe; }; +export enum FingerprintSubmissionType { + /** Report as invalid */ + INVALID = "INVALID", + /** Remove vote */ + REMOVE = "REMOVE", + /** Positive vote */ + VALID = "VALID", +} + export type FuzzyDate = { __typename: "FuzzyDate"; accuracy: DateAccuracyEnum; @@ -37075,7 +37083,7 @@ export const UnmatchFingerprintDocument = { { kind: "ObjectField", name: { kind: "Name", value: "vote" }, - value: { kind: "IntValue", value: "0" }, + value: { kind: "EnumValue", value: "REMOVE" }, }, { kind: "ObjectField", diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index b0bdc3886..a1b11e6b4 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -22,6 +22,15 @@ enum FavoriteFilter { ALL } +enum FingerprintSubmissionType { + "Positive vote" + VALID + "Report as invalid" + INVALID + "Remove vote" + REMOVE +} + type Fingerprint { hash: String! algorithm: FingerprintAlgorithm! @@ -70,9 +79,8 @@ input FingerprintQueryInput { input FingerprintSubmission { scene_id: ID! fingerprint: FingerprintInput! - unmatch: Boolean @deprecated(reason: "Use `vote` with 0 instead") - "positive for default behaviour, negative to report as invalid, zero to remove vote" - vote: Int + unmatch: Boolean @deprecated(reason: "Use `vote` with REMOVE instead") + vote: FingerprintSubmissionType = VALID } type Scene { diff --git a/pkg/models/generated_exec.go b/pkg/models/generated_exec.go index 6892d1254..0332428dd 100644 --- a/pkg/models/generated_exec.go +++ b/pkg/models/generated_exec.go @@ -4935,6 +4935,15 @@ enum FavoriteFilter { ALL } +enum FingerprintSubmissionType { + "Positive vote" + VALID + "Report as invalid" + INVALID + "Remove vote" + REMOVE +} + type Fingerprint { hash: String! algorithm: FingerprintAlgorithm! @@ -4983,9 +4992,8 @@ input FingerprintQueryInput { input FingerprintSubmission { scene_id: ID! fingerprint: FingerprintInput! - unmatch: Boolean @deprecated(reason: "Use ` + "`" + `vote` + "`" + ` with 0 instead") - "positive for default behaviour, negative to report as invalid, zero to remove vote" - vote: Int + unmatch: Boolean @deprecated(reason: "Use ` + "`" + `vote` + "`" + ` with REMOVE instead") + vote: FingerprintSubmissionType = VALID } type Scene { @@ -32055,6 +32063,10 @@ func (ec *executionContext) unmarshalInputFingerprintSubmission(ctx context.Cont asMap[k] = v } + if _, present := asMap["vote"]; !present { + asMap["vote"] = "VALID" + } + fieldsInOrder := [...]string{"scene_id", "fingerprint", "unmatch", "vote"} for _, k := range fieldsInOrder { v, ok := asMap[k] @@ -32085,7 +32097,7 @@ func (ec *executionContext) unmarshalInputFingerprintSubmission(ctx context.Cont it.Unmatch = data case "vote": ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("vote")) - data, err := ec.unmarshalOInt2ᚖint(ctx, v) + data, err := ec.unmarshalOFingerprintSubmissionType2ᚖgithubᚗcomᚋstashappᚋstashᚑboxᚋpkgᚋmodelsᚐFingerprintSubmissionType(ctx, v) if err != nil { return it, err } @@ -47200,6 +47212,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 diff --git a/pkg/models/generated_models.go b/pkg/models/generated_models.go index 79c336831..db48bc7d7 100644 --- a/pkg/models/generated_models.go +++ b/pkg/models/generated_models.go @@ -177,11 +177,10 @@ type FingerprintQueryInput struct { } type FingerprintSubmission struct { - SceneID uuid.UUID `json:"scene_id"` - Fingerprint *FingerprintInput `json:"fingerprint"` - Unmatch *bool `json:"unmatch,omitempty"` - // positive for default behaviour, negative to report as invalid, zero to remove vote - Vote *int `json:"vote,omitempty"` + SceneID uuid.UUID `json:"scene_id"` + Fingerprint *FingerprintInput `json:"fingerprint"` + Unmatch *bool `json:"unmatch,omitempty"` + Vote *FingerprintSubmissionType `json:"vote,omitempty"` } type FuzzyDate struct { @@ -1254,6 +1253,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 ( diff --git a/pkg/scene/scene.go b/pkg/scene/scene.go index 92dc38dc4..5f56af9c2 100644 --- a/pkg/scene/scene.go +++ b/pkg/scene/scene.go @@ -232,17 +232,15 @@ func Destroy(fac models.Repo, input models.SceneDestroyInput) (bool, error) { return true, nil } -// clampVote ensures that vote is between -1 and 1 -func clampVote(vote int) int { - if vote > 1 { +func submissionTypeToInt(t models.FingerprintSubmissionType) int { + switch t { + case models.FingerprintSubmissionTypeValid: return 1 - } - - if vote < -1 { + case models.FingerprintSubmissionTypeInvalid: return -1 + default: + return 0 } - - return vote } func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.FingerprintSubmission) (bool, error) { @@ -270,13 +268,14 @@ func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.Finger input.Fingerprint.UserIds = []uuid.UUID{currentUserID} } - vote := 1 + // set the default vote + vote := models.FingerprintSubmissionTypeValid if input.Vote != nil { - vote = clampVote(*input.Vote) + vote = *input.Vote } // if the user is reporting a fingerprint, ensure that the fingerprint has at least one submission - if vote == -1 { + if vote == models.FingerprintSubmissionTypeInvalid { submissionExists, err := qb.SubmittedHashExists(input.SceneID, input.Fingerprint.Hash, input.Fingerprint.Algorithm) if err != nil { return false, err @@ -287,11 +286,12 @@ func SubmitFingerprint(ctx context.Context, fac models.Repo, input models.Finger } } - sceneFingerprint := models.CreateSubmittedSceneFingerprints(scene.ID, []*models.FingerprintInput{input.Fingerprint}, vote) + 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 == 0 || (input.Unmatch != nil && *input.Unmatch) + unmatch := vote == models.FingerprintSubmissionTypeRemove || (input.Unmatch != nil && *input.Unmatch) if !unmatch { // set the new fingerprints diff --git a/pkg/sqlx/fingerprints.go b/pkg/sqlx/fingerprints.go index 149a6aafd..bb5755982 100644 --- a/pkg/sqlx/fingerprints.go +++ b/pkg/sqlx/fingerprints.go @@ -15,20 +15,20 @@ type dbSceneFingerprint struct { Vote int `db:"vote" json:"vote"` } -type DBSceneFingerprints []*dbSceneFingerprint +type dbSceneFingerprints []*dbSceneFingerprint -func (f DBSceneFingerprints) Each(fn func(interface{})) { +func (f dbSceneFingerprints) Each(fn func(interface{})) { for _, v := range f { fn(*v) } } -func (f DBSceneFingerprints) EachPtr(fn func(interface{})) { +func (f dbSceneFingerprints) EachPtr(fn func(interface{})) { for _, v := range f { fn(v) } } -func (f *DBSceneFingerprints) Add(o interface{}) { +func (f *dbSceneFingerprints) Add(o interface{}) { *f = append(*f, o.(*dbSceneFingerprint)) } diff --git a/pkg/sqlx/querybuilder_scene.go b/pkg/sqlx/querybuilder_scene.go index 3ef7e74d0..b67dfb135 100644 --- a/pkg/sqlx/querybuilder_scene.go +++ b/pkg/sqlx/querybuilder_scene.go @@ -81,13 +81,13 @@ func (qb *sceneQueryBuilder) UpdateURLs(scene uuid.UUID, updatedJoins models.Sce func (qb *sceneQueryBuilder) CreateOrReplaceFingerprints(sceneFingerprints models.SceneFingerprints) error { conflictHandling := ` - ON CONFLICT ON CONSTRAINT scene_hash_unique + ON CONFLICT ON CONSTRAINT scene_fingerprints_scene_id_fingerprint_id_user_id_key DO UPDATE SET duration = EXCLUDED.duration, vote = EXCLUDED.vote ` - var fingerprints DBSceneFingerprints + var fingerprints dbSceneFingerprints for _, fp := range sceneFingerprints { id, err := qb.getOrCreateFingerprintID(fp.Hash, fp.Algorithm) if err != nil { @@ -99,7 +99,7 @@ func (qb *sceneQueryBuilder) CreateOrReplaceFingerprints(sceneFingerprints model SceneID: fp.SceneID, UserID: fp.UserID, Duration: fp.Duration, - // TODO: vote + Vote: fp.Vote, }) } @@ -661,7 +661,8 @@ func (qb *sceneQueryBuilder) SubmittedHashExists(sceneID uuid.UUID, hash string, SELECT 1 FROM scene_fingerprints f - WHERE f.scene_id = :sceneid AND f.hash = :hash AND f.algorithm = :algorithm AND vote = 1 + 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{}{ @@ -1024,6 +1025,7 @@ 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(), }) From bd0d46f05c6540c85b10ef922de07011836521a9 Mon Sep 17 00:00:00 2001 From: WithoutPants <53250216+WithoutPants@users.noreply.github.com> Date: Sun, 25 Aug 2024 15:44:37 +1000 Subject: [PATCH 9/9] Unrelated lint fix --- pkg/models/sql_translate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/sql_translate.go b/pkg/models/sql_translate.go index 870721869..289eb3220 100644 --- a/pkg/models/sql_translate.go +++ b/pkg/models/sql_translate.go @@ -49,7 +49,7 @@ func CopyFull(target interface{}, source interface{}) { case field.Type == sourceField.Type: // direct copy targetFieldValue.Set(sourceFieldValue) - case reflect.PtrTo(field.Type) == sourceField.Type: + case reflect.PointerTo(field.Type) == sourceField.Type: // source field is pointer, target field is value // if nil, then set to zero value, otherwise copy if sourceFieldValue.IsNil() {