From 9797d7450c35341657391667e3091f72886e24cf Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 16 Dec 2020 22:53:25 -0500 Subject: [PATCH 1/4] Fix issues caused by YouTube API upgrade --- src/utils/youtubeApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index 212b6f1..690bfbc 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -12,7 +12,7 @@ _youTubeAPI.authenticate({ export class YouTubeAPI { static listVideos(videoID: string, callback: (err: string | boolean, data: any) => void) { const part = 'contentDetails,snippet'; - if (videoID.length !== 11 || videoID.includes(".")) { + if (!videoID || videoID.length !== 11 || videoID.includes(".")) { callback("Invalid video ID", undefined); return; } @@ -24,7 +24,7 @@ export class YouTubeAPI { _youTubeAPI.videos.list({ part, id: videoID, - }, (ytErr: boolean | string, data: any) => { + }, (ytErr: boolean | string, { data }: any) => { if (!ytErr) { // Only set cache if data returned if (data.items.length > 0) { From cd373f4bca471916c8695f4176724d1304f9b0e1 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 16 Dec 2020 22:53:49 -0500 Subject: [PATCH 2/4] Don't allow votes on locked videos --- src/routes/voteOnSponsorTime.ts | 12 +++++++ test/cases/voteOnSponsorTime.ts | 57 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 6c99b57..0e9def1 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -234,6 +234,18 @@ async function voteOnSponsorTime(req: Request, res: Response) { //check if user voting on own submission const isOwnSubmission = db.prepare("get", "SELECT UUID as submissionCount FROM sponsorTimes where userID = ? AND UUID = ?", [nonAnonUserID, UUID]) !== undefined; + + if (!isVIP) { + const isVideoLocked = !!db.prepare('get', 'SELECT noSegments.category from noSegments left join sponsorTimes' + + ' on (noSegments.videoID = sponsorTimes.videoID and noSegments.category = sponsorTimes.category)' + + ' where UUID = ?', [UUID]); + + if (isVideoLocked) { + res.status(403).send("Not allowed to vote on video that has been locked by a VIP."); + return; + } + } + if (type === undefined && category !== undefined) { return categoryVote(UUID, nonAnonUserID, isVIP, category, hashedIP, res); } diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 0f84c2d..8e95094 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -41,6 +41,8 @@ describe('voteOnSponsorTime', () => { db.exec(startOfQuery + "('incorrect-category', 1, 11, 500, 'incorrect-category', '" + getHash('somebody-else-id') + "', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category', 1) + "')"); db.exec(startOfQuery + "('incorrect-category-change', 1, 11, 500, 'incorrect-category-change', '" + getHash('somebody-else-id') + "', 0, 50, 'sponsor', 0, '" + getHash('incorrect-category-change', 1) + "')"); db.exec(startOfQuery + "('vote-testtesttest', 1, 11, 2, 'warnvote-uuid-0', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest', 1) + "')"); + db.exec(startOfQuery + "('no-sponsor-segments-video', 1, 11, 2, 'no-sponsor-segments-uuid-0', 'no-sponsor-segments', 0, 50, 'sponsor', 0, '" + getHash('no-sponsor-segments-video', 1) + "')"); + db.exec(startOfQuery + "('no-sponsor-segments-video', 1, 11, 2, 'no-sponsor-segments-uuid-1', 'no-sponsor-segments', 0, 50, 'intro', 0, '" + getHash('no-sponsor-segments-video', 1) + "')"); db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + now + "', '" + warnVip01Hash + "')"); db.exec(startOfWarningQuery + "('" + warnUser01Hash + "', '" + (now - 1000) + "', '" + warnVip01Hash + "')"); @@ -54,6 +56,9 @@ describe('voteOnSponsorTime', () => { db.exec("INSERT INTO vipUsers (userID) VALUES ('" + getHash("VIPUser") + "')"); privateDB.exec("INSERT INTO shadowBannedUsers (userID) VALUES ('" + getHash("randomID4") + "')"); + + db.exec("INSERT INTO noSegments (videoID, userID, category) VALUES ('no-sponsor-segments-video', 'someUser', 'sponsor')"); + }); it('Should be able to upvote a segment', (done: Done) => { @@ -375,4 +380,56 @@ describe('voteOnSponsorTime', () => { }); }); + it('Non-VIP should not be able to vote on a segment with no-segments category', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=no-segments-voter&UUID=no-sponsor-segments-uuid-0&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 403"); + } + }); + }); + + it('Non-VIP should not be able to category vote on a segment with no-segments category', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=no-segments-voter&UUID=no-sponsor-segments-uuid-0&category=outro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 403) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 403"); + } + }); + }); + + it('VIP should able to vote on a segment with no-segments category', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=VIPUser&UUID=no-sponsor-segments-uuid-0&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 200"); + } + }); + }); + + it('Non-VIP should be able to vote on a segment on a no-segments video with a category that doesn\'t have no-segments', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=no-segments-voter&UUID=no-sponsor-segments-uuid-1&type=1", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + done(); + } else { + done("Status code was " + res.statusCode + " instead of 200"); + } + }); + }); + }); From 5deda4603eb09ec584622b72d92afa6f2858d172 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 16 Dec 2020 23:00:11 -0500 Subject: [PATCH 3/4] Allow submitter to change category immediately --- src/routes/voteOnSponsorTime.ts | 6 +++--- test/cases/voteOnSponsorTime.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 0e9def1..2286a7f 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -131,7 +131,7 @@ function sendWebhooks(voteData: VoteData) { } } -function categoryVote(UUID: string, userID: string, isVIP: boolean, category: any, hashedIP: string, res: Response) { +function categoryVote(UUID: string, userID: string, isVIP: boolean, isOwnSubmission: boolean, category: string, hashedIP: string, res: Response) { // Check if they've already made a vote const previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]); @@ -198,7 +198,7 @@ function categoryVote(UUID: string, userID: string, isVIP: boolean, category: an //TODO: In the future, raise this number from zero to make it harder to change categories // VIPs change it every time - if (nextCategoryCount - currentCategoryCount >= (submissionInfo ? Math.max(Math.ceil(submissionInfo.votes / 2), 1) : 1) || isVIP) { + if (nextCategoryCount - currentCategoryCount >= (submissionInfo ? Math.max(Math.ceil(submissionInfo.votes / 2), 1) : 1) || isVIP || isOwnSubmission) { // Replace the category db.prepare('run', "update sponsorTimes set category = ? where UUID = ?", [category, UUID]); } @@ -247,7 +247,7 @@ async function voteOnSponsorTime(req: Request, res: Response) { } if (type === undefined && category !== undefined) { - return categoryVote(UUID, nonAnonUserID, isVIP, category, hashedIP, res); + return categoryVote(UUID, nonAnonUserID, isVIP, isOwnSubmission, category, hashedIP, res); } if (type == 1 && !isVIP && !isOwnSubmission) { diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 8e95094..11c0f70 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -30,6 +30,7 @@ describe('voteOnSponsorTime', () => { db.exec(startOfQuery + "('vote-testtesttest,test', 1, 11, 100, 'vote-uuid-3', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-testtesttest,test', 1) + "')"); db.exec(startOfQuery + "('vote-test3', 1, 11, 2, 'vote-uuid-4', 'testman', 0, 50, 'sponsor', 0, '" + getHash('vote-test3', 1) + "')"); db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-test3', 1) + "')"); + db.exec(startOfQuery + "('vote-test3', 7, 22, -3, 'vote-uuid-5_1', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-test3', 1) + "')"); db.exec(startOfQuery + "('vote-multiple', 1, 11, 2, 'vote-uuid-6', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); db.exec(startOfQuery + "('vote-multiple', 20, 33, 2, 'vote-uuid-7', 'testman', 0, 50, 'intro', 0, '" + getHash('vote-multiple', 1) + "')"); db.exec(startOfQuery + "('voter-submitter', 1, 11, 2, 'vote-uuid-8', '" + getHash("randomID") + "', 0, 50, 'sponsor', 0, '" + getHash('voter-submitter', 1) + "')"); @@ -323,6 +324,24 @@ describe('voteOnSponsorTime', () => { }); }); + it('Submitter should be able to vote for a category and it should immediately change', (done: Done) => { + request.get(getbaseURL() + + "/api/voteOnSponsorTime?userID=testman&UUID=vote-uuid-5_1&category=outro", null, + (err, res) => { + if (err) done(err); + else if (res.statusCode === 200) { + let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-5"]); + if (row.category === "outro") { + done(); + } else { + done("Vote did not succeed. Submission went from intro to " + row.category + "."); + } + } else { + done("Status code was " + res.statusCode); + } + }); + }); + it('Should not be able to category-vote on an invalid UUID submission', (done: Done) => { request.get(getbaseURL() + "/api/voteOnSponsorTime?userID=randomID3&UUID=invalid-uuid&category=intro", null, From a7f04ad73252a850d2acb4942a354f2bb54a4e35 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 16 Dec 2020 23:47:47 -0500 Subject: [PATCH 4/4] Update category test + fix double voting issue Fix #190 --- src/routes/voteOnSponsorTime.ts | 16 +++++++++------- test/cases/voteOnSponsorTime.ts | 32 +++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 2286a7f..46ab33a 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -133,9 +133,9 @@ function sendWebhooks(voteData: VoteData) { function categoryVote(UUID: string, userID: string, isVIP: boolean, isOwnSubmission: boolean, category: string, hashedIP: string, res: Response) { // Check if they've already made a vote - const previousVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]); + const usersLastVoteInfo = privateDB.prepare('get', "select count(*) as votes, category from categoryVotes where UUID = ? and userID = ?", [UUID, userID]); - if (previousVoteInfo !== undefined && previousVoteInfo.category === category) { + if (usersLastVoteInfo?.category === category) { // Double vote, ignore res.sendStatus(200); return; @@ -153,6 +153,8 @@ function categoryVote(UUID: string, userID: string, isVIP: boolean, isOwnSubmiss return; } + const nextCategoryInfo = db.prepare("get", "select votes from categoryVotes where UUID = ? and category = ?", [UUID, category]); + const timeSubmitted = Date.now(); const voteAmount = isVIP ? 500 : 1; @@ -167,11 +169,11 @@ function categoryVote(UUID: string, userID: string, isVIP: boolean, isOwnSubmiss } // Add the info into the private db - if (previousVoteInfo !== undefined) { + if (usersLastVoteInfo?.votes > 0) { // Reverse the previous vote - db.prepare('run', "update categoryVotes set votes = votes - ? where UUID = ? and category = ?", [voteAmount, UUID, previousVoteInfo.category]); + db.prepare('run', "update categoryVotes set votes = votes - ? where UUID = ? and category = ?", [voteAmount, UUID, usersLastVoteInfo.category]); - privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ? where userID = ?", [category, timeSubmitted, hashedIP, userID]); + privateDB.prepare('run', "update categoryVotes set category = ?, timeSubmitted = ?, hashedIP = ? where userID = ? and UUID = ?", [category, timeSubmitted, hashedIP, userID, UUID]); } else { privateDB.prepare('run', "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, userID, hashedIP, category, timeSubmitted]); } @@ -194,11 +196,11 @@ function categoryVote(UUID: string, userID: string, isVIP: boolean, isOwnSubmiss privateDB.prepare("run", "insert into categoryVotes (UUID, userID, hashedIP, category, timeSubmitted) values (?, ?, ?, ?, ?)", [UUID, submissionInfo.userID, "unknown", currentCategory.category, submissionInfo.timeSubmitted]); } - const nextCategoryCount = (previousVoteInfo.votes || 0) + voteAmount; + const nextCategoryCount = (nextCategoryInfo?.votes || 0) + voteAmount; //TODO: In the future, raise this number from zero to make it harder to change categories // VIPs change it every time - if (nextCategoryCount - currentCategoryCount >= (submissionInfo ? Math.max(Math.ceil(submissionInfo.votes / 2), 1) : 1) || isVIP || isOwnSubmission) { + if (nextCategoryCount - currentCategoryCount >= Math.max(Math.ceil(submissionInfo?.votes / 2), 2) || isVIP || isOwnSubmission) { // Replace the category db.prepare('run', "update sponsorTimes set category = ? where UUID = ?", [category, UUID]); } diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 11c0f70..29a28e3 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -224,25 +224,26 @@ describe('voteOnSponsorTime', () => { }); }); - /** Test needs to be updated with new category vote limit - it('Should be able to vote for a category and it should immediately change (for now)', (done: Done) => { + it('Should be able to vote for a category and it should add your vote to the database', (done: Done) => { request.get(getbaseURL() + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=intro", null, (err, res) => { if (err) done(err); else if (res.statusCode === 200) { let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); - if (row.category === "intro") { + let categoryRows = db.prepare('all', "SELECT votes, category FROM categoryVotes WHERE UUID = ?", ["vote-uuid-4"]); + if (row.category === "sponsor" && categoryRows.length === 2 + && categoryRows[0]?.votes === 1 && categoryRows[0]?.category === "intro" + && categoryRows[1]?.votes === 1 && categoryRows[1]?.category === "sponsor") { done(); } else { - done("Vote did not succeed. Submission went from sponsor to " + row.category); + done("Submission changed to " + row.category + " instead of staying as sponsor. Vote was applied as " + categoryRows[0]?.category + " with " + categoryRows[0]?.votes + " votes and there were " + categoryRows.length + " rows."); } } else { done("Status code was " + res.statusCode); } }); }); - */ it('Should not able to change to an invalid category', (done: Done) => { request.get(getbaseURL() @@ -262,25 +263,34 @@ describe('voteOnSponsorTime', () => { }); }); - /** Test needs to be updated with new category vote limit - it('Should be able to change your vote for a category and it should immediately change (for now)', (done: Done) => { + it('Should be able to change your vote for a category and it should add your vote to the database', (done: Done) => { request.get(getbaseURL() + "/api/voteOnSponsorTime?userID=randomID2&UUID=vote-uuid-4&category=outro", null, (err, res) => { if (err) done(err); else if (res.statusCode === 200) { - let row = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); - if (row.category === "outro") { + let submissionRow = db.prepare('get', "SELECT category FROM sponsorTimes WHERE UUID = ?", ["vote-uuid-4"]); + let categoryRows = db.prepare('all', "SELECT votes, category FROM categoryVotes WHERE UUID = ?", ["vote-uuid-4"]); + let introVotes = 0; + let outroVotes = 0; + let sponsorVotes = 0; + for (const row of categoryRows) { + if (row?.category === "intro") introVotes += row?.votes; + if (row?.category === "outro") outroVotes += row?.votes; + if (row?.category === "sponsor") sponsorVotes += row?.votes; + } + if (submissionRow.category === "sponsor" && categoryRows.length === 3 + && introVotes === 0 && outroVotes === 1 && sponsorVotes === 1) { done(); } else { - done("Vote did not succeed. Submission went from intro to " + row.category); + done("Submission changed to " + submissionRow.category + " instead of staying as sponsor. There were " + + introVotes + " intro votes, " + outroVotes + " outro votes and " + sponsorVotes + " sponsor votes."); } } else { done("Status code was " + res.statusCode); } }); }); - */ it('Should not be able to change your vote to an invalid category', (done: Done) => {