From b04e0dcd97382753826fcb788bf81cc8949dfef8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 3 Jan 2024 01:13:35 -0500 Subject: [PATCH] DeArrow downvotes --- databases/_upgrade_sponsorTimes_39.sql | 11 + src/routes/getBranding.ts | 20 +- src/routes/postBranding.ts | 67 ++++-- src/types/branding.model.ts | 3 + test/cases/getBranding.ts | 57 ++--- test/cases/postBranding.ts | 287 ++++++++++++++++++++++++- 6 files changed, 389 insertions(+), 56 deletions(-) create mode 100644 databases/_upgrade_sponsorTimes_39.sql diff --git a/databases/_upgrade_sponsorTimes_39.sql b/databases/_upgrade_sponsorTimes_39.sql new file mode 100644 index 0000000..2e048c2 --- /dev/null +++ b/databases/_upgrade_sponsorTimes_39.sql @@ -0,0 +1,11 @@ +BEGIN TRANSACTION; + +ALTER TABLE "titleVotes" ADD "downvotes" INTEGER default 0; +ALTER TABLE "titleVotes" ADD "removed" INTEGER default 0; + +ALTER TABLE "thumbnailVotes" ADD "downvotes" INTEGER default 0; +ALTER TABLE "thumbnailVotes" ADD "removed" INTEGER default 0; + +UPDATE "config" SET value = 39 WHERE key = 'version'; + +COMMIT; diff --git a/src/routes/getBranding.ts b/src/routes/getBranding.ts index 3013c40..82b67aa 100644 --- a/src/routes/getBranding.ts +++ b/src/routes/getBranding.ts @@ -24,18 +24,18 @@ enum BrandingSubmissionType { export async function getVideoBranding(res: Response, videoID: VideoID, service: Service, ip: IPAddress, returnUserID: boolean): Promise { const getTitles = () => db.prepare( "all", - `SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification", "titles"."userID" + `SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."downvotes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification", "titles"."userID" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" - WHERE "titles"."videoID" = ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1`, + WHERE "titles"."videoID" = ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1 AND "titleVotes"."votes" - "titleVotes"."downvotes" > -2 AND "titleVotes"."removed" = 0`, [videoID, service], { useReplica: true } ) as Promise; const getThumbnails = () => db.prepare( "all", - `SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID", "thumbnails"."userID" + `SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."downvotes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID", "thumbnails"."userID" FROM "thumbnails" LEFT JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" LEFT JOIN "thumbnailTimestamps" ON "thumbnails"."UUID" = "thumbnailTimestamps"."UUID" - WHERE "thumbnails"."videoID" = ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" > -2 + WHERE "thumbnails"."videoID" = ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" - "thumbnailVotes"."downvotes" > -2 AND "thumbnailVotes"."removed" = 0 ORDER BY "thumbnails"."timeSubmitted" ASC`, [videoID, service], { useReplica: true } @@ -89,18 +89,18 @@ export async function getVideoBranding(res: Response, videoID: VideoID, service: export async function getVideoBrandingByHash(videoHashPrefix: VideoIDHash, service: Service, ip: IPAddress, returnUserID: boolean): Promise> { const getTitles = () => db.prepare( "all", - `SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification" + `SELECT "titles"."title", "titles"."original", "titleVotes"."votes", "titleVotes"."downvotes", "titleVotes"."locked", "titleVotes"."shadowHidden", "titles"."UUID", "titles"."videoID", "titles"."hashedVideoID", "titleVotes"."verification" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" - WHERE "titles"."hashedVideoID" LIKE ? AND "titles"."service" = ? AND "titleVotes"."votes" > -2`, + WHERE "titles"."hashedVideoID" LIKE ? AND "titles"."service" = ? AND "titleVotes"."votes" > -1 AND "titleVotes"."votes" - "titleVotes"."downvotes" > -2 AND "titleVotes"."removed" = 0`, [`${videoHashPrefix}%`, service], { useReplica: true } ) as Promise; const getThumbnails = () => db.prepare( "all", - `SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID" + `SELECT "thumbnailTimestamps"."timestamp", "thumbnails"."original", "thumbnailVotes"."votes", "thumbnailVotes"."downvotes", "thumbnailVotes"."locked", "thumbnailVotes"."shadowHidden", "thumbnails"."UUID", "thumbnails"."videoID", "thumbnails"."hashedVideoID" FROM "thumbnails" LEFT JOIN "thumbnailVotes" ON "thumbnails"."UUID" = "thumbnailVotes"."UUID" LEFT JOIN "thumbnailTimestamps" ON "thumbnails"."UUID" = "thumbnailTimestamps"."UUID" - WHERE "thumbnails"."hashedVideoID" LIKE ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" > -2 + WHERE "thumbnails"."hashedVideoID" LIKE ? AND "thumbnails"."service" = ? AND "thumbnailVotes"."votes" - "thumbnailVotes"."downvotes" > -2 AND "thumbnailVotes"."removed" = 0 ORDER BY "thumbnails"."timeSubmitted" ASC`, [`${videoHashPrefix}%`, service], { useReplica: true } @@ -176,7 +176,7 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, db .map((r) => ({ title: r.title, original: r.original === 1, - votes: r.votes + r.verification, + votes: r.votes + r.verification - r.downvotes, locked: r.locked === 1, UUID: r.UUID, userID: returnUserID ? r.userID : undefined @@ -191,7 +191,7 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, db .map((r) => ({ timestamp: r.timestamp, original: r.original === 1, - votes: r.votes, + votes: r.votes - r.downvotes, locked: r.locked === 1, UUID: r.UUID, userID: returnUserID ? r.userID : undefined diff --git a/src/routes/postBranding.ts b/src/routes/postBranding.ts index 0d46c96..b9213b2 100644 --- a/src/routes/postBranding.ts +++ b/src/routes/postBranding.ts @@ -24,6 +24,11 @@ enum BrandingType { Thumbnail } +enum BrandingVoteType { + Upvote = 1, + Downvote = 2 +} + interface ExistingVote { UUID: BrandingUUID; type: number; @@ -31,7 +36,7 @@ interface ExistingVote { } export async function postBranding(req: Request, res: Response) { - const { videoID, userID, title, thumbnail, autoLock } = req.body as BrandingSubmission; + const { videoID, userID, title, thumbnail, autoLock, downvote } = req.body as BrandingSubmission; const service = getService(req.body.service); if (!videoID || !userID || userID.length < 30 || !service @@ -57,7 +62,7 @@ export async function postBranding(req: Request, res: Response) { } const now = Date.now(); - const voteType = 1; + const voteType: BrandingVoteType = downvote ? BrandingVoteType.Downvote : BrandingVoteType.Upvote; if (title && !isVip && title.title.length > config.maxTitleLength) { lock.unlock(); @@ -74,10 +79,14 @@ export async function postBranding(req: Request, res: Response) { if (existingUUID != undefined && isBanned) return; // ignore votes on existing details from banned users const UUID = existingUUID || crypto.randomUUID(); - const existingVote = await handleExistingVotes(BrandingType.Title, videoID, hashedUserID, UUID, hashedIP, voteType); + await handleExistingVotes(BrandingType.Title, videoID, hashedUserID, UUID, hashedIP, voteType); if (existingUUID) { - await updateVoteTotals(BrandingType.Title, existingVote, UUID, shouldLock); + await updateVoteTotals(BrandingType.Title, UUID, shouldLock, !!downvote); } else { + if (downvote) { + throw new Error("Title submission doesn't exist"); + } + await db.prepare("run", `INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [videoID, title.title, title.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]); @@ -111,10 +120,14 @@ export async function postBranding(req: Request, res: Response) { if (existingUUID != undefined && isBanned) return; // ignore votes on existing details from banned users const UUID = existingUUID || crypto.randomUUID(); - const existingVote = await handleExistingVotes(BrandingType.Thumbnail, videoID, hashedUserID, UUID, hashedIP, voteType); + await handleExistingVotes(BrandingType.Thumbnail, videoID, hashedUserID, UUID, hashedIP, voteType); if (existingUUID) { - await updateVoteTotals(BrandingType.Thumbnail, existingVote, UUID, shouldLock); + await updateVoteTotals(BrandingType.Thumbnail, UUID, shouldLock, !!downvote); } else { + if (downvote) { + throw new Error("Thumbnail submission doesn't exist"); + } + await db.prepare("run", `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`, [videoID, thumbnail.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]); @@ -152,38 +165,54 @@ export async function postBranding(req: Request, res: Response) { * If no existing vote, it adds one. */ async function handleExistingVotes(type: BrandingType, videoID: VideoID, - hashedUserID: HashedUserID, UUID: BrandingUUID, hashedIP: HashedIP, voteType: number): Promise { + hashedUserID: HashedUserID, UUID: BrandingUUID, hashedIP: HashedIP, voteType: BrandingVoteType) { const table = type === BrandingType.Title ? `"titleVotes"` : `"thumbnailVotes"`; - const existingVote = await privateDB.prepare("get", `SELECT "id", "UUID", "type" from ${table} where "videoID" = ? AND "userID" = ?`, [videoID, hashedUserID]); - if (existingVote && existingVote.UUID !== UUID) { - if (existingVote.type === 1) { - await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" - 1 WHERE "UUID" = ?`, [existingVote.UUID]); + // Either votes of the same type, or on the same submission (undo a downvote) + const existingVotes = await privateDB.prepare("all", `SELECT "id", "UUID", "type" from ${table} where "videoID" = ? AND "userID" = ? AND ("type" = ? OR "UUID" = ?)`, [videoID, hashedUserID, voteType, UUID]) as ExistingVote[]; + if (existingVotes.length > 0) { + // Only one upvote per video + if (voteType === BrandingVoteType.Upvote) { + for (const existingVote of existingVotes) { + switch (existingVote.type) { + case BrandingVoteType.Upvote: + await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" - 1 WHERE "UUID" = ?`, [existingVote.UUID]); + await privateDB.prepare("run", `UPDATE ${table} SET "type" = ?, "UUID" = ? WHERE "id" = ?`, [voteType, UUID, existingVote.id]); + break; + case BrandingVoteType.Downvote: + // Undoing a downvote now that it is being upvoted + await db.prepare("run", `UPDATE ${table} SET "downvotes" = "downvotes" - 1 WHERE "UUID" = ?`, [existingVote.UUID]); + await privateDB.prepare("run", `DELETE FROM ${table} WHERE "id" = ?`, [existingVote.id]); + break; + } + } } - await privateDB.prepare("run", `UPDATE ${table} SET "type" = ?, "UUID" = ? WHERE "id" = ?`, [voteType, UUID, existingVote.id]); - } else if (!existingVote) { + } else { await privateDB.prepare("run", `INSERT INTO ${table} ("videoID", "UUID", "userID", "hashedIP", "type") VALUES (?, ?, ?, ?, ?)`, [videoID, UUID, hashedUserID, hashedIP, voteType]); } - - return existingVote; } /** * Only called if an existing vote exists. * Will update public vote totals and locked status. */ -async function updateVoteTotals(type: BrandingType, existingVote: ExistingVote, UUID: BrandingUUID, shouldLock: boolean): Promise { +async function updateVoteTotals(type: BrandingType, UUID: BrandingUUID, shouldLock: boolean, downvote: boolean): Promise { const table = type === BrandingType.Title ? `"titleVotes"` : `"thumbnailVotes"`; - // Don't upvote if we vote on the same submission - if (!existingVote || existingVote.UUID !== UUID) { + if (downvote) { + await db.prepare("run", `UPDATE ${table} SET "downvotes" = "downvotes" + 1 WHERE "UUID" = ?`, [UUID]); + } else { await db.prepare("run", `UPDATE ${table} SET "votes" = "votes" + 1 WHERE "UUID" = ?`, [UUID]); } if (shouldLock) { - await db.prepare("run", `UPDATE ${table} SET "locked" = 1 WHERE "UUID" = ?`, [UUID]); + if (downvote) { + await db.prepare("run", `UPDATE ${table} SET "removed" = 1 WHERE "UUID" = ?`, [UUID]); + } else { + await db.prepare("run", `UPDATE ${table} SET "locked" = 1, "removed" = 0 WHERE "UUID" = ?`, [UUID]); + } } } diff --git a/src/types/branding.model.ts b/src/types/branding.model.ts index 98367a1..fd656e8 100644 --- a/src/types/branding.model.ts +++ b/src/types/branding.model.ts @@ -17,6 +17,7 @@ export interface TitleDBResult extends BrandingDBSubmission { title: string, original: number, votes: number, + downvotes: number, locked: number, verification: number, userID: UserID @@ -35,6 +36,7 @@ export interface ThumbnailDBResult extends BrandingDBSubmission { timestamp?: number, original: number, votes: number, + downvotes: number, locked: number, userID: UserID } @@ -84,6 +86,7 @@ export interface BrandingSubmission { userID: UserID; service: Service; autoLock: boolean | undefined; + downvote: boolean | undefined; } export interface BrandingSegmentDBResult { diff --git a/test/cases/getBranding.ts b/test/cases/getBranding.ts index 1758cd4..fc4b868 100644 --- a/test/cases/getBranding.ts +++ b/test/cases/getBranding.ts @@ -36,29 +36,34 @@ describe("getBranding", () => { before(async () => { const titleQuery = `INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`; - const titleVotesQuery = `INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden", "verification") VALUES (?, ?, ?, ?, ?)`; + const titleVotesQuery = `INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden", "verification", "downvotes", "removed") VALUES (?, ?, ?, ?, ?, ?, ?)`; const thumbnailQuery = `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`; const thumbnailTimestampsQuery = `INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)`; - const thumbnailVotesQuery = `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, ?, ?, ?)`; + const thumbnailVotesQuery = `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden", "downvotes", "removed") VALUES (?, ?, ?, ?, ?, ?)`; await Promise.all([ db.prepare("run", titleQuery, [videoID1, "title1", 0, "userID1", Service.YouTube, videoID1Hash, 1, "UUID1"]), db.prepare("run", titleQuery, [videoID1, "title2", 0, "userID2", Service.YouTube, videoID1Hash, 1, "UUID2"]), db.prepare("run", titleQuery, [videoID1, "title3", 1, "userID3", Service.YouTube, videoID1Hash, 1, "UUID3"]), + db.prepare("run", titleQuery, [videoID1, "title4removed", 0, "userID4", Service.YouTube, videoID1Hash, 1, "UUID4"]), db.prepare("run", thumbnailQuery, [videoID1, 0, "userID1", Service.YouTube, videoID1Hash, 1, "UUID1T"]), db.prepare("run", thumbnailQuery, [videoID1, 1, "userID2", Service.YouTube, videoID1Hash, 1, "UUID2T"]), db.prepare("run", thumbnailQuery, [videoID1, 0, "userID3", Service.YouTube, videoID1Hash, 1, "UUID3T"]), + db.prepare("run", thumbnailQuery, [videoID1, 0, "userID4", Service.YouTube, videoID1Hash, 1, "UUID4T"]), ]); await Promise.all([ - db.prepare("run", titleVotesQuery, ["UUID1", 3, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID2", 2, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID3", 1, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID1", 3, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID2", 3, 0, 0, 0, 1, 0]), + db.prepare("run", titleVotesQuery, ["UUID3", 1, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID4", 5, 0, 0, 0, 0, 1]), db.prepare("run", thumbnailTimestampsQuery, ["UUID1T", 1]), db.prepare("run", thumbnailTimestampsQuery, ["UUID3T", 3]), - db.prepare("run", thumbnailVotesQuery, ["UUID1T", 3, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID2T", 2, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID3T", 1, 0, 0]) + db.prepare("run", thumbnailTimestampsQuery, ["UUID4T", 18]), + db.prepare("run", thumbnailVotesQuery, ["UUID1T", 3, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID2T", 3, 0, 0, 1, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID3T", 1, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID4T", 5, 0, 0, 0, 1]) ]); await Promise.all([ @@ -71,15 +76,15 @@ describe("getBranding", () => { ]); await Promise.all([ - db.prepare("run", titleVotesQuery, ["UUID11", 3, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID21", 2, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID31", 1, 1, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID11", 3, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID21", 2, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID31", 1, 1, 0, 0, 0, 0]), db.prepare("run", thumbnailTimestampsQuery, ["UUID11T", 1]), db.prepare("run", thumbnailTimestampsQuery, ["UUID31T", 3]), - db.prepare("run", thumbnailVotesQuery, ["UUID11T", 3, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID21T", 2, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID31T", 1, 1, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID11T", 3, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID21T", 2, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID31T", 1, 1, 0, 0, 0]), ]); await Promise.all([ @@ -92,14 +97,14 @@ describe("getBranding", () => { ]); await Promise.all([ - db.prepare("run", titleVotesQuery, ["UUID12", 3, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID22", 2, 0, 0, 0]), - db.prepare("run", titleVotesQuery, ["UUID32", 1, 0, 1, 0]), + db.prepare("run", titleVotesQuery, ["UUID12", 3, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID22", 2, 0, 0, 0, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID32", 1, 0, 1, 0, 0, 0]), db.prepare("run", thumbnailTimestampsQuery, ["UUID12T", 1]), db.prepare("run", thumbnailTimestampsQuery, ["UUID32T", 3]), - db.prepare("run", thumbnailVotesQuery, ["UUID12T", 3, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID22T", 2, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID32T", 1, 0, 1]) + db.prepare("run", thumbnailVotesQuery, ["UUID12T", 3, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID22T", 2, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID32T", 1, 0, 1, 0, 0]) ]); const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "hidden", "shadowHidden", "description", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; @@ -116,14 +121,14 @@ describe("getBranding", () => { ]); await Promise.all([ - db.prepare("run", titleVotesQuery, ["UUID-uv-1", 3, 0, 0, -1]), - db.prepare("run", titleVotesQuery, ["UUID-uv-2", 2, 0, 0, -1]), - db.prepare("run", titleVotesQuery, ["UUID-uv-3", 0, 0, 0, -1]), + db.prepare("run", titleVotesQuery, ["UUID-uv-1", 3, 0, 0, -1, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID-uv-2", 2, 0, 0, -1, 0, 0]), + db.prepare("run", titleVotesQuery, ["UUID-uv-3", 0, 0, 0, -1, 0, 0]), db.prepare("run", thumbnailTimestampsQuery, ["UUID-uv-1T", 1]), db.prepare("run", thumbnailTimestampsQuery, ["UUID-uv-3T", 3]), - db.prepare("run", thumbnailVotesQuery, ["UUID-uv-1T", 3, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID-uv-2T", 2, 0, 0]), - db.prepare("run", thumbnailVotesQuery, ["UUID-uv-3T", 1, 0, 0]) + db.prepare("run", thumbnailVotesQuery, ["UUID-uv-1T", 3, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID-uv-2T", 2, 0, 0, 0, 0]), + db.prepare("run", thumbnailVotesQuery, ["UUID-uv-3T", 1, 0, 0, 0, 0]) ]); }); diff --git a/test/cases/postBranding.ts b/test/cases/postBranding.ts index a393515..1aa881d 100644 --- a/test/cases/postBranding.ts +++ b/test/cases/postBranding.ts @@ -54,6 +54,21 @@ describe("postBranding", () => { await db.prepare("run", insertThumbnailVotesQuery, ["postBrandLocked1", 0, 1, 0]); await db.prepare("run", insertThumbnailVotesQuery, ["postBrandLocked2", 0, 1, 0]); + // Testing vip submission removal + await db.prepare("run", insertTitleQuery, ["postBrandRemoved1", "Some title", 0, getHash(userID1), Service.YouTube, getHash("postBrandRemoved1"), Date.now(), "postBrandRemoved1"]); + await db.prepare("run", insertTitleVotesQuery, ["postBrandRemoved1", 0, 1, 0, 0]); + await db.prepare("run", insertTitleQuery, ["postBrandRemoved1", "Some other title", 0, getHash(userID1), Service.YouTube, getHash("postBrandRemoved1"), Date.now(), "postBrandRemoved2"]); + await db.prepare("run", insertTitleVotesQuery, ["postBrandRemoved2", 0, 1, 0, 0]); + + // Testing vip submission removal + const insertThumbnailTimestampQuery = 'INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)'; + await db.prepare("run", insertThumbnailQuery, ["postBrandRemoved1", 0, getHash(userID3), Service.YouTube, getHash("postBrandRemoved1"), Date.now(), "postBrandRemoved1"]); + await db.prepare("run", insertThumbnailTimestampQuery, ["postBrandRemoved1", 12.34]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandRemoved1", 0, 1, 0]); + await db.prepare("run", insertThumbnailQuery, ["postBrandRemoved1", 0, getHash(userID3), Service.YouTube, getHash("postBrandRemoved1"), Date.now(), "postBrandRemoved2"]); + await db.prepare("run", insertThumbnailTimestampQuery, ["postBrandRemoved2", 13.34]); + await db.prepare("run", insertThumbnailVotesQuery, ["postBrandRemoved2", 0, 1, 0]); + // Verified through title submissions await db.prepare("run", insertTitleQuery, ["postBrandVerified1", "Some title", 0, getHash(userID7), Service.YouTube, getHash("postBrandVerified1"), Date.now(), "postBrandVerified1"]); await db.prepare("run", insertTitleQuery, ["postBrandVerified2", "Some title", 1, getHash(userID7), Service.YouTube, getHash("postBrandVerified2"), Date.now(), "postBrandVerified2"]); @@ -75,7 +90,6 @@ describe("postBranding", () => { await db.prepare("run", insertThumbnailQuery, ["postBrandBannedOriginalVote", 1, getHash(userID1), Service.YouTube, getHash("postBrandBannedOriginalVote"), Date.now(), "postBrandBannedOriginalVote"]); await db.prepare("run", insertThumbnailVotesQuery, ["postBrandBannedCustomVote", 0, 0, 0]); await db.prepare("run", insertThumbnailVotesQuery, ["postBrandBannedOriginalVote", 0, 0, 0]); - const insertThumbnailTimestampQuery = 'INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)'; await db.prepare("run", insertThumbnailTimestampQuery, ["postBrandBannedCustomVote", 12.34]); }); @@ -127,6 +141,7 @@ describe("postBranding", () => { assert.strictEqual(dbTitle.original, title.original ? 1 : 0); assert.strictEqual(dbVotes.votes, 0); + assert.strictEqual(dbVotes.downvotes, 0); assert.strictEqual(dbVotes.locked, 0); assert.strictEqual(dbVotes.shadowHidden, 0); }); @@ -223,6 +238,177 @@ describe("postBranding", () => { assert.strictEqual(dbThumbnailVotes.shadowHidden, 0); }); + it("Submit another title and thumbnail", async () => { + const videoID = "postBrand5"; + const title = { + title: "Some other title", + original: false + }; + const thumbnail = { + timestamp: 13.42, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: userID4, + service: Service.YouTube, + videoID + }); + + assert.strictEqual(res.status, 200); + const dbTitle = await queryTitleByVideo(videoID); + const dbTitleVotes = await queryTitleVotesByUUID(dbTitle.UUID); + const dbThumbnail = await queryThumbnailByVideo(videoID); + const dbThumbnailTimestamps = await queryThumbnailTimestampsByUUID(dbThumbnail.UUID); + const dbThumbnailVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); + + assert.strictEqual(dbTitle.title, title.title); + assert.strictEqual(dbTitle.original, title.original ? 1 : 0); + + assert.strictEqual(dbTitleVotes.votes, 0); + assert.strictEqual(dbTitleVotes.locked, 0); + assert.strictEqual(dbTitleVotes.shadowHidden, 0); + + assert.strictEqual(dbThumbnailTimestamps.timestamp, thumbnail.timestamp); + assert.strictEqual(dbThumbnail.original, thumbnail.original ? 1 : 0); + + assert.strictEqual(dbThumbnailVotes.votes, 0); + assert.strictEqual(dbThumbnailVotes.locked, 0); + assert.strictEqual(dbThumbnailVotes.shadowHidden, 0); + }); + + it("Downvote title and thumbnail", async () => { + const videoID = "postBrand5"; + const title = { + title: "Some other title", + original: false + }; + const thumbnail = { + timestamp: 13.42, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: userID6, + service: Service.YouTube, + videoID, + downvote: true + }); + + assert.strictEqual(res.status, 200); + const dbTitles = await queryTitleByVideo(videoID, true); + for (const dbTitle of dbTitles) { + if (dbTitle.title === title.title) { + const dbTitleVotes = await queryTitleVotesByUUID(dbTitle.UUID); + assert.strictEqual(dbTitleVotes.votes, 0); + assert.strictEqual(dbTitleVotes.downvotes, 1); + assert.strictEqual(dbTitleVotes.locked, 0); + assert.strictEqual(dbTitleVotes.shadowHidden, 0); + } + } + + const dbThumbnails = await queryThumbnailByVideo(videoID, true); + for (const dbThumbnail of dbThumbnails) { + if (dbThumbnail.timestamp === thumbnail.timestamp) { + const dbThumbnailVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); + + assert.strictEqual(dbThumbnailVotes.votes, 0); + assert.strictEqual(dbThumbnailVotes.downvotes, 1); + assert.strictEqual(dbThumbnailVotes.locked, 0); + assert.strictEqual(dbThumbnailVotes.shadowHidden, 0); + } + } + }); + + it("Downvote another title and thumbnail", async () => { + const videoID = "postBrand5"; + const title = { + title: "Some title", + original: false + }; + const thumbnail = { + timestamp: 12.42, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: userID6, + service: Service.YouTube, + videoID, + downvote: true + }); + + assert.strictEqual(res.status, 200); + const dbTitles = await queryTitleByVideo(videoID, true); + for (const dbTitle of dbTitles) { + const dbTitleVotes = await queryTitleVotesByUUID(dbTitle.UUID); + assert.strictEqual(dbTitleVotes.votes, 0); + assert.strictEqual(dbTitleVotes.downvotes, 1); + assert.strictEqual(dbTitleVotes.locked, 0); + assert.strictEqual(dbTitleVotes.shadowHidden, 0); + } + + const dbThumbnails = await queryThumbnailByVideo(videoID, true); + for (const dbThumbnail of dbThumbnails) { + const dbThumbnailVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); + + assert.strictEqual(dbThumbnailVotes.votes, 0); + assert.strictEqual(dbThumbnailVotes.downvotes, 1); + assert.strictEqual(dbThumbnailVotes.locked, 0); + assert.strictEqual(dbThumbnailVotes.shadowHidden, 0); + } + }); + + it("Upvote after downvoting title and thumbnail", async () => { + const videoID = "postBrand5"; + const title = { + title: "Some other title", + original: false + }; + const thumbnail = { + timestamp: 13.42, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: userID6, + service: Service.YouTube, + videoID + }); + + assert.strictEqual(res.status, 200); + const dbTitles = await queryTitleByVideo(videoID, true); + for (const dbTitle of dbTitles) { + if (dbTitle.title === title.title) { + const dbTitleVotes = await queryTitleVotesByUUID(dbTitle.UUID); + assert.strictEqual(dbTitleVotes.votes, 1); + assert.strictEqual(dbTitleVotes.downvotes, 0); + assert.strictEqual(dbTitleVotes.locked, 0); + assert.strictEqual(dbTitleVotes.shadowHidden, 0); + } + } + + const dbThumbnails = await queryThumbnailByVideo(videoID, true); + for (const dbThumbnail of dbThumbnails) { + if (dbThumbnail.timestamp === thumbnail.timestamp) { + const dbThumbnailVotes = await queryThumbnailVotesByUUID(dbThumbnail.UUID); + + assert.strictEqual(dbThumbnailVotes.votes, 1); + assert.strictEqual(dbThumbnailVotes.downvotes, 0); + assert.strictEqual(dbThumbnailVotes.locked, 0); + assert.strictEqual(dbThumbnailVotes.shadowHidden, 0); + } + } + }); + it("Submit title and thumbnail as VIP", async () => { const videoID = "postBrand6"; const title = { @@ -350,6 +536,104 @@ describe("postBranding", () => { assert.strictEqual(dbThumbnailVotes.locked, 0); }); + it("Downvote title and thumbnail as VIP", async () => { + const videoID = "postBrandRemoved1"; + const title = { + title: "Some title", + original: false + }; + const thumbnail = { + timestamp: 12.34, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: vipUser, + service: Service.YouTube, + videoID, + downvote: true + }); + + assert.strictEqual(res.status, 200); + + const otherSegmentTitleVotes1 = await queryTitleVotesByUUID("postBrandRemoved1"); + const otherSegmentTitleVotes2 = await queryTitleVotesByUUID("postBrandRemoved2"); + const otherSegmentThumbnailVotes1 = await queryThumbnailVotesByUUID("postBrandRemoved1"); + const otherSegmentThumbnailVotes2 = await queryThumbnailVotesByUUID("postBrandRemoved2"); + + assert.strictEqual(otherSegmentTitleVotes1.removed, 1); + assert.strictEqual(otherSegmentTitleVotes2.removed, 0); + assert.strictEqual(otherSegmentThumbnailVotes1.removed, 1); + assert.strictEqual(otherSegmentThumbnailVotes2.removed, 0); + }); + + it("Downvote another title and thumbnail as VIP", async () => { + const videoID = "postBrandRemoved1"; + const title = { + title: "Some other title", + original: false + }; + const thumbnail = { + timestamp: 13.34, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: vipUser, + service: Service.YouTube, + videoID, + downvote: true + }); + + assert.strictEqual(res.status, 200); + + const otherSegmentTitleVotes1 = await queryTitleVotesByUUID("postBrandRemoved1"); + const otherSegmentTitleVotes2 = await queryTitleVotesByUUID("postBrandRemoved2"); + const otherSegmentThumbnailVotes1 = await queryThumbnailVotesByUUID("postBrandRemoved1"); + const otherSegmentThumbnailVotes2 = await queryThumbnailVotesByUUID("postBrandRemoved2"); + + assert.strictEqual(otherSegmentTitleVotes1.removed, 1); + assert.strictEqual(otherSegmentTitleVotes2.removed, 1); + assert.strictEqual(otherSegmentThumbnailVotes1.removed, 1); + assert.strictEqual(otherSegmentThumbnailVotes2.removed, 1); + }); + + it("Remove downvote on title and thumbnail as VIP", async () => { + const videoID = "postBrandRemoved1"; + const title = { + title: "Some title", + original: false + }; + const thumbnail = { + timestamp: 12.34, + original: false + }; + + const res = await postBranding({ + title, + thumbnail, + userID: vipUser, + service: Service.YouTube, + videoID + }); + + assert.strictEqual(res.status, 200); + + const otherSegmentTitleVotes1 = await queryTitleVotesByUUID("postBrandRemoved1"); + const otherSegmentTitleVotes2 = await queryTitleVotesByUUID("postBrandRemoved2"); + const otherSegmentThumbnailVotes1 = await queryThumbnailVotesByUUID("postBrandRemoved1"); + const otherSegmentThumbnailVotes2 = await queryThumbnailVotesByUUID("postBrandRemoved2"); + + assert.strictEqual(otherSegmentTitleVotes1.removed, 0); + assert.strictEqual(otherSegmentTitleVotes2.removed, 1); + assert.strictEqual(otherSegmentThumbnailVotes1.removed, 0); + assert.strictEqual(otherSegmentThumbnailVotes2.removed, 1); + }); + it("Vote the same title again", async () => { const videoID = "postBrand1"; const title = { @@ -516,6 +800,7 @@ describe("postBranding", () => { assert.strictEqual(dbTitle.original, title.original ? 1 : 0); assert.strictEqual(dbVotes.votes, 1); + assert.strictEqual(dbVotes.downvotes, 0); assert.strictEqual(dbVotes.locked, 0); assert.strictEqual(dbVotes.shadowHidden, 0); });