Skip to content

Commit 5f9b4c8

Browse files
committed
Make casual downvotes apply to all categories
1 parent d608125 commit 5f9b4c8

File tree

7 files changed

+119
-68
lines changed

7 files changed

+119
-68
lines changed

databases/_upgrade_private_12.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
BEGIN TRANSACTION;
2+
3+
ALTER TABLE "casualVotes" DROP COLUMN "type";
4+
5+
UPDATE "config" SET value = 12 WHERE key = 'version';
6+
7+
COMMIT;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
BEGIN TRANSACTION;
2+
3+
ALTER TABLE "casualVotes" DROP COLUMN "downvotes";
4+
5+
UPDATE "config" SET value = 42 WHERE key = 'version';
6+
7+
COMMIT;

src/routes/getBranding.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export async function getVideoBranding(res: Response, videoID: VideoID, service:
5353

5454
const getCasualVotes = () => db.prepare(
5555
"all",
56-
`SELECT "category", "upvotes", "downvotes" FROM "casualVotes"
56+
`SELECT "category", "upvotes" FROM "casualVotes"
5757
WHERE "videoID" = ? AND "service" = ?
5858
ORDER BY "timeSubmitted" ASC`,
5959
[videoID, service],
@@ -131,7 +131,7 @@ export async function getVideoBrandingByHash(videoHashPrefix: VideoIDHash, servi
131131

132132
const getCasualVotes = () => db.prepare(
133133
"all",
134-
`SELECT "videoID", "category", "upvotes", "downvotes" FROM "casualVotes"
134+
`SELECT "videoID", "category", "upvotes" FROM "casualVotes"
135135
WHERE "hashedVideoID" LIKE ? AND "service" = ?
136136
ORDER BY "timeSubmitted" ASC`,
137137
[`${videoHashPrefix}%`, service],
@@ -230,9 +230,10 @@ async function filterAndSortBranding(videoID: VideoID, returnUserID: boolean, fe
230230
}))
231231
.filter((a) => (fetchAll && !a.original) || a.votes >= 1 || (a.votes >= 0 && !a.original) || a.locked) as ThumbnailResult[];
232232

233-
const casualVotes = dbCasualVotes.map((r) => ({
233+
const casualDownvotes = dbCasualVotes.filter((r) => r.category === "downvote")[0];
234+
const casualVotes = dbCasualVotes.filter((r) => r.category !== "downvote").map((r) => ({
234235
id: r.category,
235-
count: r.upvotes - r.downvotes
236+
count: r.upvotes - (casualDownvotes?.upvotes ?? 0)
236237
})).filter((a) => a.count > 0);
237238

238239
const videoDuration = dbSegments.filter(s => s.videoDuration !== 0)[0]?.videoDuration ?? null;

src/routes/postCasual.ts

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,25 @@ import { QueryCacher } from "../utils/queryCacher";
1414
import { acquireLock } from "../utils/redisLock";
1515
import { checkBanStatus } from "../utils/checkBan";
1616

17-
enum CasualVoteType {
18-
Upvote = 1,
19-
Downvote = 2
20-
}
21-
2217
interface ExistingVote {
2318
UUID: BrandingUUID;
2419
type: number;
2520
}
2621

2722
export async function postCasual(req: Request, res: Response) {
28-
const { videoID, userID, downvote, categories } = req.body as CasualVoteSubmission;
23+
const { videoID, userID, downvote } = req.body as CasualVoteSubmission;
24+
let categories = req.body.categories as CasualCategory[];
2925
const service = getService(req.body.service);
3026

27+
if (downvote) {
28+
categories = ["downvote" as CasualCategory];
29+
} else if (!categories.every((c) => config.casualCategoryList.includes(c))) {
30+
return res.status(400).send("Invalid category");
31+
}
32+
3133
if (!videoID || !userID || userID.length < 30 || !service || !categories || !Array.isArray(categories)) {
3234
return res.status(400).send("Bad Request");
3335
}
34-
if (!categories.every((c) => config.casualCategoryList.includes(c))) {
35-
return res.status(400).send("Invalid category");
36-
}
3736

3837
try {
3938
const hashedUserID = await getHashCache(userID);
@@ -52,24 +51,18 @@ export async function postCasual(req: Request, res: Response) {
5251
}
5352

5453
const now = Date.now();
55-
const voteType: CasualVoteType = downvote ? CasualVoteType.Downvote : CasualVoteType.Upvote;
56-
5754
for (const category of categories) {
5855
const existingUUID = (await db.prepare("get", `SELECT "UUID" from "casualVotes" where "videoID" = ? AND "category" = ?`, [videoID, category]))?.UUID;
5956
const UUID = existingUUID || crypto.randomUUID();
6057

61-
const alreadyVotedTheSame = await handleExistingVotes(videoID, service, UUID, hashedUserID, hashedIP, category, voteType, now);
58+
const alreadyVotedTheSame = await handleExistingVotes(videoID, service, UUID, hashedUserID, hashedIP, category, downvote, now);
6259
if (existingUUID) {
6360
if (!alreadyVotedTheSame) {
64-
if (downvote) {
65-
await db.prepare("run", `UPDATE "casualVotes" SET "downvotes" = "downvotes" + 1 WHERE "UUID" = ?`, [UUID]);
66-
} else {
67-
await db.prepare("run", `UPDATE "casualVotes" SET "upvotes" = "upvotes" + 1 WHERE "UUID" = ?`, [UUID]);
68-
}
61+
await db.prepare("run", `UPDATE "casualVotes" SET "upvotes" = "upvotes" + 1 WHERE "UUID" = ?`, [UUID]);
6962
}
7063
} else {
71-
await db.prepare("run", `INSERT INTO "casualVotes" ("videoID", "service", "hashedVideoID", "timeSubmitted", "UUID", "category", "upvotes", "downvotes") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
72-
[videoID, service, hashedVideoID, now, UUID, category, downvote ? 0 : 1, downvote ? 1 : 0]);
64+
await db.prepare("run", `INSERT INTO "casualVotes" ("videoID", "service", "hashedVideoID", "timeSubmitted", "UUID", "category", "upvotes") VALUES (?, ?, ?, ?, ?, ?, ?)`,
65+
[videoID, service, hashedVideoID, now, UUID, category, 1]);
7366
}
7467
}
7568

@@ -85,24 +78,30 @@ export async function postCasual(req: Request, res: Response) {
8578
}
8679

8780
async function handleExistingVotes(videoID: VideoID, service: Service, UUID: string,
88-
hashedUserID: HashedUserID, hashedIP: HashedIP, category: CasualCategory, voteType: CasualVoteType, now: number): Promise<boolean> {
89-
const existingVote = await privateDB.prepare("get", `SELECT "UUID", "type" from "casualVotes" WHERE "videoID" = ? AND "service" = ? AND "userID" = ? AND category = ?`, [videoID, service, hashedUserID, category]) as ExistingVote;
81+
hashedUserID: HashedUserID, hashedIP: HashedIP, category: CasualCategory, downvote: boolean, now: number): Promise<boolean> {
82+
const existingVote = await privateDB.prepare("get", `SELECT "UUID" from "casualVotes" WHERE "videoID" = ? AND "service" = ? AND "userID" = ? AND "category" = ?`, [videoID, service, hashedUserID, category]) as ExistingVote;
9083
if (existingVote) {
91-
if (existingVote.type === voteType) {
92-
return true;
93-
}
94-
95-
if (existingVote.type === CasualVoteType.Upvote) {
96-
await db.prepare("run", `UPDATE "casualVotes" SET "upvotes" = "upvotes" - 1 WHERE "UUID" = ?`, [UUID]);
84+
return true;
85+
} else {
86+
if (downvote) {
87+
// Remove upvotes for all categories on this video
88+
const existingUpvotes = await privateDB.prepare("all", `SELECT "category" from "casualVotes" WHERE "category" != 'downvote' AND "videoID" = ? AND "service" = ? AND "userID" = ?`, [videoID, service, hashedUserID]);
89+
for (const existingUpvote of existingUpvotes) {
90+
await db.prepare("run", `UPDATE "casualVotes" SET "upvotes" = "upvotes" - 1 WHERE "videoID" = ? AND "category" = ?`, [videoID, existingUpvote.category]);
91+
await privateDB.prepare("run", `DELETE FROM "casualVotes" WHERE "videoID" = ? AND "userID" = ? AND "category" = ?`, [videoID, hashedUserID, existingUpvote.category]);
92+
}
9793
} else {
98-
await db.prepare("run", `UPDATE "casualVotes" SET "downvotes" = "downvotes" - 1 WHERE "UUID" = ?`, [UUID]);
94+
// Undo a downvote if it exists
95+
const existingDownvote = await privateDB.prepare("get", `SELECT "UUID" from "casualVotes" WHERE "category" = 'downvote' AND "videoID" = ? AND "service" = ? AND "userID" = ?`, [videoID, service, hashedUserID]) as ExistingVote;
96+
if (existingDownvote) {
97+
await db.prepare("run", `UPDATE "casualVotes" SET "upvotes" = "upvotes" - 1 WHERE "category" = 'downvote' AND "videoID" = ?`, [videoID]);
98+
await privateDB.prepare("run", `DELETE FROM "casualVotes" WHERE "category" = 'downvote' AND "videoID" = ? AND "userID" = ?`, [videoID, hashedUserID]);
99+
}
99100
}
100-
101-
await privateDB.prepare("run", `DELETE FROM "casualVotes" WHERE "UUID" = ?`, [existingVote.UUID]);
102101
}
103102

104-
await privateDB.prepare("run", `INSERT INTO "casualVotes" ("videoID", "service", "userID", "hashedIP", "category", "type", "timeSubmitted") VALUES (?, ?, ?, ?, ?, ?, ?)`,
105-
[videoID, service, hashedUserID, hashedIP, category, voteType, now]);
103+
await privateDB.prepare("run", `INSERT INTO "casualVotes" ("videoID", "service", "userID", "hashedIP", "category", "timeSubmitted") VALUES (?, ?, ?, ?, ?, ?)`,
104+
[videoID, service, hashedUserID, hashedIP, category, now]);
106105

107106
return false;
108107
}

src/types/branding.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { UserID } from "./user.model";
33

44
export type BrandingUUID = string & { readonly __brandingUUID: unique symbol };
55

6-
export type CasualCategory = ("funny" | "creative" | "clever" | "descriptive" | "other") & { __casualCategoryBrand: unknown };
6+
export type CasualCategory = ("funny" | "creative" | "clever" | "descriptive" | "other" | "downvote") & { __casualCategoryBrand: unknown };
77

88
export interface BrandingDBSubmissionData {
99
videoID: VideoID,

test/cases/getBranding.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe("getBranding", () => {
4747
const thumbnailTimestampsQuery = `INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)`;
4848
const thumbnailVotesQuery = `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden", "downvotes", "removed") VALUES (?, ?, ?, ?, ?, ?)`;
4949
const segmentQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "hidden", "shadowHidden", "description", "hashedVideoID") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
50-
const insertCasualVotesQuery = `INSERT INTO "casualVotes" ("UUID", "videoID", "service", "hashedVideoID", "category", "upvotes", "downvotes", "timeSubmitted") VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
50+
const insertCasualVotesQuery = `INSERT INTO "casualVotes" ("UUID", "videoID", "service", "hashedVideoID", "category", "upvotes", "timeSubmitted") VALUES (?, ?, ?, ?, ?, ?, ?)`;
5151

5252
await Promise.all([
5353
db.prepare("run", titleQuery, [videoID1, "title1", 0, "userID1", Service.YouTube, videoID1Hash, 1, "UUID1"]),
@@ -150,9 +150,10 @@ describe("getBranding", () => {
150150
]);
151151

152152
await Promise.all([
153-
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual1", videoIDCasual, Service.YouTube, videoIDCasualHash, "clever", 1, 0, Date.now()]),
154-
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual2", videoIDCasualDownvoted, Service.YouTube, videoIDCasualDownvotedHash, "clever", 1, 1, Date.now()]),
155-
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual3", videoIDCasualDownvoted, Service.YouTube, videoIDCasualDownvotedHash, "other", 4, 1, Date.now()])
153+
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual1", videoIDCasual, Service.YouTube, videoIDCasualHash, "clever", 1, Date.now()]),
154+
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual2", videoIDCasualDownvoted, Service.YouTube, videoIDCasualDownvotedHash, "clever", 1, Date.now()]),
155+
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual2d", videoIDCasualDownvoted, Service.YouTube, videoIDCasualDownvotedHash, "downvote", 1, Date.now()]),
156+
db.prepare("run", insertCasualVotesQuery, ["postBrandCasual3", videoIDCasualDownvoted, Service.YouTube, videoIDCasualDownvotedHash, "other", 4, Date.now()]),
156157
]);
157158
});
158159

test/cases/postCasual.ts

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("postCasual", () => {
1616
data
1717
});
1818

19-
const queryCasualVotesByVideo = (videoID: string, all = false) => db.prepare(all ? "all" : "get", `SELECT * FROM "casualVotes" WHERE "videoID" = ? ORDER BY "timeSubmitted" DESC`, [videoID]);
19+
const queryCasualVotesByVideo = (videoID: string, all = false) => db.prepare(all ? "all" : "get", `SELECT * FROM "casualVotes" WHERE "videoID" = ? ORDER BY "timeSubmitted" ASC`, [videoID]);
2020

2121
it("submit casual vote", async () => {
2222
const videoID = "postCasual1";
@@ -33,7 +33,6 @@ describe("postCasual", () => {
3333

3434
assert.strictEqual(dbVotes.category, "clever");
3535
assert.strictEqual(dbVotes.upvotes, 1);
36-
assert.strictEqual(dbVotes.downvotes, 0);
3736
});
3837

3938
it("submit same casual vote again", async () => {
@@ -51,7 +50,6 @@ describe("postCasual", () => {
5150

5251
assert.strictEqual(dbVotes.category, "clever");
5352
assert.strictEqual(dbVotes.upvotes, 1);
54-
assert.strictEqual(dbVotes.downvotes, 0);
5553
});
5654

5755
it("submit casual upvote", async () => {
@@ -69,45 +67,46 @@ describe("postCasual", () => {
6967

7068
assert.strictEqual(dbVotes.category, "clever");
7169
assert.strictEqual(dbVotes.upvotes, 2);
72-
assert.strictEqual(dbVotes.downvotes, 0);
7370
});
7471

7572
it("submit casual downvote from same user", async () => {
7673
const videoID = "postCasual1";
7774

7875
const res = await postCasual({
79-
categories: ["clever"],
8076
downvote: true,
8177
userID: userID1,
8278
service: Service.YouTube,
8379
videoID
8480
});
8581

8682
assert.strictEqual(res.status, 200);
87-
const dbVotes = await queryCasualVotesByVideo(videoID);
83+
const dbVotes = await queryCasualVotesByVideo(videoID, true);
8884

89-
assert.strictEqual(dbVotes.category, "clever");
90-
assert.strictEqual(dbVotes.upvotes, 1);
91-
assert.strictEqual(dbVotes.downvotes, 1);
85+
assert.strictEqual(dbVotes[0].category, "clever");
86+
assert.strictEqual(dbVotes[0].upvotes, 1);
87+
88+
assert.strictEqual(dbVotes[1].category, "downvote");
89+
assert.strictEqual(dbVotes[1].upvotes, 1);
9290
});
9391

9492
it("submit casual downvote from different user", async () => {
9593
const videoID = "postCasual1";
9694

9795
const res = await postCasual({
98-
categories: ["clever"],
9996
downvote: true,
10097
userID: userID3,
10198
service: Service.YouTube,
10299
videoID
103100
});
104101

105102
assert.strictEqual(res.status, 200);
106-
const dbVotes = await queryCasualVotesByVideo(videoID);
103+
const dbVotes = await queryCasualVotesByVideo(videoID, true);
107104

108-
assert.strictEqual(dbVotes.category, "clever");
109-
assert.strictEqual(dbVotes.upvotes, 1);
110-
assert.strictEqual(dbVotes.downvotes, 2);
105+
assert.strictEqual(dbVotes[0].category, "clever");
106+
assert.strictEqual(dbVotes[0].upvotes, 1);
107+
108+
assert.strictEqual(dbVotes[1].category, "downvote");
109+
assert.strictEqual(dbVotes[1].upvotes, 2);
111110
});
112111

113112
it("submit casual upvote from same user", async () => {
@@ -122,11 +121,13 @@ describe("postCasual", () => {
122121
});
123122

124123
assert.strictEqual(res.status, 200);
125-
const dbVotes = await queryCasualVotesByVideo(videoID);
124+
const dbVotes = await queryCasualVotesByVideo(videoID, true);
126125

127-
assert.strictEqual(dbVotes.category, "clever");
128-
assert.strictEqual(dbVotes.upvotes, 2);
129-
assert.strictEqual(dbVotes.downvotes, 1);
126+
assert.strictEqual(dbVotes[0].category, "clever");
127+
assert.strictEqual(dbVotes[0].upvotes, 2);
128+
129+
assert.strictEqual(dbVotes[1].category, "downvote");
130+
assert.strictEqual(dbVotes[1].upvotes, 1);
130131
});
131132

132133
it("submit multiple casual votes", async () => {
@@ -144,34 +145,69 @@ describe("postCasual", () => {
144145

145146
assert.strictEqual(dbVotes[0].category, "clever");
146147
assert.strictEqual(dbVotes[0].upvotes, 1);
147-
assert.strictEqual(dbVotes[0].downvotes, 0);
148148

149149
assert.strictEqual(dbVotes[1].category, "other");
150150
assert.strictEqual(dbVotes[1].upvotes, 1);
151-
assert.strictEqual(dbVotes[1].downvotes, 0);
152151
});
153152

154-
it("submit multiple casual downvotes", async () => {
155-
const videoID = "postCasual3";
153+
it("downvote on video with previous votes with multiple categories", async () => {
154+
const videoID = "postCasual2";
156155

157156
const res = await postCasual({
158-
categories: ["clever", "other"],
157+
downvote: true,
159158
userID: userID1,
160159
service: Service.YouTube,
161-
videoID,
162-
downvote: true
160+
videoID
163161
});
164162

165163
assert.strictEqual(res.status, 200);
166164
const dbVotes = await queryCasualVotesByVideo(videoID, true);
167165

168166
assert.strictEqual(dbVotes[0].category, "clever");
169167
assert.strictEqual(dbVotes[0].upvotes, 0);
170-
assert.strictEqual(dbVotes[0].downvotes, 1);
171168

172169
assert.strictEqual(dbVotes[1].category, "other");
173170
assert.strictEqual(dbVotes[1].upvotes, 0);
174-
assert.strictEqual(dbVotes[1].downvotes, 1);
171+
172+
assert.strictEqual(dbVotes[2].category, "downvote");
173+
assert.strictEqual(dbVotes[2].upvotes, 1);
174+
});
175+
176+
it("upvote on video with previous downvotes with multiple categories", async () => {
177+
const videoID = "postCasual2";
178+
179+
const res = await postCasual({
180+
categories: ["clever", "other"],
181+
userID: userID1,
182+
service: Service.YouTube,
183+
videoID
184+
});
185+
186+
assert.strictEqual(res.status, 200);
187+
const dbVotes = await queryCasualVotesByVideo(videoID, true);
188+
189+
assert.strictEqual(dbVotes[0].category, "clever");
190+
assert.strictEqual(dbVotes[0].upvotes, 1);
191+
192+
assert.strictEqual(dbVotes[1].category, "other");
193+
assert.strictEqual(dbVotes[1].upvotes, 1);
194+
});
195+
196+
it("downvote on video with no existing votes", async () => {
197+
const videoID = "postCasual3";
198+
199+
const res = await postCasual({
200+
userID: userID1,
201+
service: Service.YouTube,
202+
videoID,
203+
downvote: true
204+
});
205+
206+
assert.strictEqual(res.status, 200);
207+
const dbVotes = await queryCasualVotesByVideo(videoID);
208+
209+
assert.strictEqual(dbVotes.category, "downvote");
210+
assert.strictEqual(dbVotes.upvotes, 1);
175211
});
176212

177213
});

0 commit comments

Comments
 (0)