From 4600b8a599bba7e0f775f02917f63e2e88c587f4 Mon Sep 17 00:00:00 2001 From: Michael C Date: Tue, 15 Aug 2023 19:45:17 -0400 Subject: [PATCH] show stats to shadowhidden users --- src/routes/getUserInfo.ts | 4 +- src/routes/getUserStats.ts | 6 ++- test/cases/getUserInfo.ts | 29 +++++++++++++ test/cases/getUserStats.ts | 89 ++++++++++++++++++++++++++++++-------- 4 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index fce3e33..97acd96 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -12,10 +12,12 @@ const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { try { + const userBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount; + const countShadowHidden = userBanCount > 0 ? 2 : 1; // if shadowbanned, count shadowhidden as well const row = await db.prepare("get", `SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", count(*) as "segmentCount" FROM "sponsorTimes" - WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [maxRewardTime, maxRewardTime, userID], { useReplica: true }); + WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != ?`, [maxRewardTime, maxRewardTime, userID, countShadowHidden], { useReplica: true }); if (row.minutesSaved != null) { return { minutesSaved: row.minutesSaved, diff --git a/src/routes/getUserStats.ts b/src/routes/getUserStats.ts index b93cc17..ca6f279 100644 --- a/src/routes/getUserStats.ts +++ b/src/routes/getUserStats.ts @@ -34,13 +34,15 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea `; } try { + const userBanCount = (await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount; + const countShadowHidden = userBanCount > 0 ? 2 : 1; // if shadowbanned, count shadowhidden as well const row = await db.prepare("get", ` SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", ${additionalQuery} count(*) as "segmentCount" FROM "sponsorTimes" - WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, - [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]); + WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != ?`, + [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID, countShadowHidden]); const source = (row.minutesSaved != null) ? row : {}; const handler = { get: (target: Record, name: string) => target?.[name] || 0 }; const proxy = new Proxy(source, handler); diff --git a/test/cases/getUserInfo.ts b/test/cases/getUserInfo.ts index 8bfc567..6df7443 100644 --- a/test/cases/getUserInfo.ts +++ b/test/cases/getUserInfo.ts @@ -22,6 +22,8 @@ describe("getUserInfo", () => { await db.prepare("run", sponsorTimesQuery, ["getUserInfo3", 1, 11, 2, "uuid000006", getHash("getuserinfo_user_02"), 6, 10, "sponsor", "skip", 0]); await db.prepare("run", sponsorTimesQuery, ["getUserInfo4", 1, 11, 2, "uuid000010", getHash("getuserinfo_user_04"), 9, 10, "chapter", "chapter", 0]); await db.prepare("run", sponsorTimesQuery, ["getUserInfo5", 1, 11, 2, "uuid000011", getHash("getuserinfo_user_05"), 9, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo6", 1, 11, 2, "uuid000012", getHash("getuserinfo_user_06"), 9, 10, "sponsor", "skip", 1]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo7", 1, 11, 2, "uuid000013", getHash("getuserinfo_ban_02"), 9, 10, "sponsor", "skip", 1]); const titlesQuery = 'INSERT INTO "titles" ("videoID", "title", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?, ?)'; const titleVotesQuery = 'INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, ?, ?, 0);'; @@ -44,6 +46,7 @@ describe("getUserInfo", () => { const insertBanQuery = 'INSERT INTO "shadowBannedUsers" ("userID") VALUES (?)'; await db.prepare("run", insertBanQuery, [getHash("getuserinfo_ban_01")]); + await db.prepare("run", insertBanQuery, [getHash("getuserinfo_ban_02")]); }); it("Should be able to get a 200", (done) => { @@ -390,4 +393,30 @@ describe("getUserInfo", () => { }) .catch(err => done(err)); }); + + it("Should return all segments of banned user", (done) => { + client.get(endpoint, { params: { userID: "getuserinfo_ban_02", value: ["segmentCount"] } }) + .then(res => { + assert.strictEqual(res.status, 200); + const expected = { + segmentCount: 1 + }; + assert.ok(partialDeepEquals(res.data, expected)); + done(); // pass + }) + .catch(err => done(err)); + }); + + it("Should not return shadowhidden segments of not-banned user", (done) => { + client.get(endpoint, { params: { userID: "getuserinfo_user_06", value: ["segmentCount"] } }) + .then(res => { + assert.strictEqual(res.status, 200); + const expected = { + segmentCount: 0 + }; + assert.ok(partialDeepEquals(res.data, expected)); + done(); // pass + }) + .catch(err => done(err)); + }); }); diff --git a/test/cases/getUserStats.ts b/test/cases/getUserStats.ts index f5d63d3..0db720c 100644 --- a/test/cases/getUserStats.ts +++ b/test/cases/getUserStats.ts @@ -4,25 +4,44 @@ import { getHash } from "../../src/utils/getHash"; import assert from "assert"; import { client } from "../utils/httpClient"; +const userOnePrivateID = "getuserstats_user_01"; +const userOnePublicID = getHash(userOnePrivateID); +const userTwoPrivateID = "getuserstats_user_02"; +const userTwoPublicID = getHash(userTwoPrivateID); +const userThreePrivateID = "getuserstats_user_03"; +const userThreePublicID = getHash(userThreePrivateID); +const userFourPrivateID = "getuserstats_user_04"; +const userFourPublicID = getHash(userFourPrivateID); + describe("getUserStats", () => { const endpoint = "/api/userStats"; before(async () => { + const insertBanQuery = 'INSERT INTO "shadowBannedUsers" ("userID") VALUES (?)'; + await db.prepare("run", insertBanQuery, [userThreePublicID]); + const insertUserNameQuery = 'INSERT INTO "userNames" ("userID", "userName") VALUES(?, ?)'; - await db.prepare("run", insertUserNameQuery, [getHash("getuserstats_user_01"), "Username user 01"]); + await db.prepare("run", insertUserNameQuery, [userOnePublicID, "Username user 01"]); const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "actionType", "UUID", "userID", "timeSubmitted", views, category, "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid1", getHash("getuserstats_user_01"), 1, 1, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid2", getHash("getuserstats_user_01"), 2, 2, "selfpromo", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid3", getHash("getuserstats_user_01"), 3, 3, "interaction", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid4", getHash("getuserstats_user_01"), 4, 4, "intro", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid5", getHash("getuserstats_user_01"), 5, 5, "outro", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid6", getHash("getuserstats_user_01"), 6, 6, "preview", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid7", getHash("getuserstats_user_01"), 7, 7, "music_offtopic", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 11, 11, 0, "poi", "getuserstatsuuid8", getHash("getuserstats_user_01"), 8, 8, "poi_highlight", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, -2, "skip", "getuserstatsuuid9", getHash("getuserstats_user_02"), 8, 2, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid10", getHash("getuserstats_user_01"), 8, 2, "filler", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 0, 0, "full", "getuserstatsuuid11", getHash("getuserstats_user_01"), 8, 2, "exclusive_access", 0]); - await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "chapter", "getuserstatsuuid12", getHash("getuserstats_user_01"), 9, 2, "chapter", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid1", userOnePublicID, 1, 1, "sponsor", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid2", userOnePublicID, 2, 2, "selfpromo", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid3", userOnePublicID, 3, 3, "interaction", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid4", userOnePublicID, 4, 4, "intro", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid5", userOnePublicID, 5, 5, "outro", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid6", userOnePublicID, 6, 6, "preview", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid7", userOnePublicID, 7, 7, "music_offtopic", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 11, 11, 0, "poi", "getuserstatsuuid8", userOnePublicID, 8, 8, "poi_highlight", 0]); + await db.prepare("run", sponsorTimesQuery, [userTwoPrivateID, 0, 60, -2, "skip", "getuserstatsuuid9", userTwoPublicID, 8, 2, "sponsor", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "skip", "getuserstatsuuid10", userOnePublicID, 8, 2, "filler", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 0, 0, "full", "getuserstatsuuid11", userOnePublicID, 8, 2, "exclusive_access", 0]); + await db.prepare("run", sponsorTimesQuery, [userOnePrivateID, 0, 60, 0, "chapter", "getuserstatsuuid12", userOnePublicID, 9, 2, "chapter", 0]); + + // fully banned user + await db.prepare("run", sponsorTimesQuery, [userThreePrivateID, 0, 60, 0, "skip", "getuserstatsuuid13", userThreePublicID, 1, 1, "sponsor", 1]); + await db.prepare("run", sponsorTimesQuery, [userThreePrivateID, 0, 60, 0, "skip", "getuserstatsuuid14", userThreePublicID, 1, 1, "sponsor", 1]); + // user with banned segments + await db.prepare("run", sponsorTimesQuery, [userFourPrivateID, 0, 60, 0, "skip", "getuserstatsuuid15", userFourPublicID, 1, 1, "sponsor", 0]); + await db.prepare("run", sponsorTimesQuery, [userFourPrivateID, 0, 60, 0, "skip", "getuserstatsuuid16", userFourPublicID, 1, 1, "sponsor", 1]); }); it("Should be able to get a 400 (No userID parameter)", (done) => { @@ -35,12 +54,12 @@ describe("getUserStats", () => { }); it("Should be able to get all user info", (done) => { - client.get(endpoint, { params: { userID: "getuserstats_user_01", fetchCategoryStats: true, fetchActionTypeStats: true } }) + client.get(endpoint, { params: { userID: userOnePrivateID, fetchCategoryStats: true, fetchActionTypeStats: true } }) .then(res => { assert.strictEqual(res.status, 200); const expected = { userName: "Username user 01", - userID: getHash("getuserstats_user_01"), + userID: userOnePublicID, categoryCount: { sponsor: 1, selfpromo: 1, @@ -88,7 +107,7 @@ describe("getUserStats", () => { }); it("Should be able to get all zeroes for only ignored segments", (done) => { - client.get(endpoint, { params: { userID: "getuserstats_user_02" } }) + client.get(endpoint, { params: { userID: userTwoPrivateID } }) .then(res => { assert.strictEqual(res.status, 200); const data = res.data; @@ -103,7 +122,7 @@ describe("getUserStats", () => { }); it("Should not get extra stats if not requested", (done) => { - client.get(endpoint, { params: { userID: "getuserstats_user_01" } }) + client.get(endpoint, { params: { userID: userOnePrivateID } }) .then(res => { assert.strictEqual(res.status, 200); const data = res.data; @@ -117,7 +136,7 @@ describe("getUserStats", () => { }); it("Should get parts of extra stats if not requested", (done) => { - client.get(endpoint, { params: { userID: "getuserstats_user_01", fetchActionTypeStats: true } }) + client.get(endpoint, { params: { userID: userOnePrivateID, fetchActionTypeStats: true } }) .then(res => { assert.strictEqual(res.status, 200); const data = res.data; @@ -129,4 +148,38 @@ describe("getUserStats", () => { }) .catch(err => done(err)); }); + + it("Should return stats for banned segments if user is banned", (done) => { + client.get(endpoint, { params: { userID: userThreePrivateID } }) + .then(res => { + assert.strictEqual(res.status, 200); + const expected = { + userID: userThreePublicID, + overallStats: { + minutesSaved: 2, + segmentCount: 2 + } + }; + assert.ok(partialDeepEquals(res.data, expected)); + done(); + }) + .catch(err => done(err)); + }); + + it("Should not return stats for banned segments", (done) => { + client.get(endpoint, { params: { userID: userFourPrivateID } }) + .then(res => { + assert.strictEqual(res.status, 200); + const expected = { + userID: userFourPublicID, + overallStats: { + minutesSaved: 1, + segmentCount: 1 + } + }; + assert.ok(partialDeepEquals(res.data, expected)); + done(); + }) + .catch(err => done(err)); + }); });