From e6bf20937d82b9d16daee888c34114be81cd3c8f Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 31 Mar 2022 03:47:06 -0400 Subject: [PATCH 1/8] endpoint + tests for getVideoLabels --- src/app.ts | 6 + src/routes/getVideoLabel.ts | 176 ++++++++++++++++++++++++++++++ src/routes/getVideoLabelByHash.ts | 27 +++++ src/utils/redisKeys.ts | 12 +- test/cases/getVideoLabels.ts | 170 +++++++++++++++++++++++++++++ 5 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 src/routes/getVideoLabel.ts create mode 100644 src/routes/getVideoLabelByHash.ts create mode 100644 test/cases/getVideoLabels.ts diff --git a/src/app.ts b/src/app.ts index 267e44d..c922cb4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,6 +48,8 @@ import { getRating } from "./routes/ratings/getRating"; import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache"; import { getTopCategoryUsers } from "./routes/getTopCategoryUsers"; import { addUserAsTempVIP } from "./routes/addUserAsTempVIP"; +import { endpoint as getVideoLabels } from "./routes/getVideoLabel"; +import { getVideoLabelsByHash } from "./routes/getVideoLabelByHash"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -202,6 +204,10 @@ function setupRoutes(router: Router) { router.post("/api/ratings/rate", postRateEndpoints); router.post("/api/ratings/clearCache", ratingPostClearCache); + // labels + router.get("/api/videoLabels", getVideoLabels); + router.get("/api/videoLabels/:prefix", getVideoLabelsByHash); + if (config.postgres) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); diff --git a/src/routes/getVideoLabel.ts b/src/routes/getVideoLabel.ts new file mode 100644 index 0000000..fc98c5b --- /dev/null +++ b/src/routes/getVideoLabel.ts @@ -0,0 +1,176 @@ +import { Request, Response } from "express"; +import { db } from "../databases/databases"; +import { videoLabelsHashKey, videoLabelsKey } from "../utils/redisKeys"; +import { SBRecord } from "../types/lib.model"; +import { DBSegment, Segment, Service, VideoData, VideoID, VideoIDHash } from "../types/segments.model"; +import { Logger } from "../utils/logger"; +import { QueryCacher } from "../utils/queryCacher"; +import { getService } from "../utils/getService"; + +function transformDBSegments(segments: DBSegment[]): Segment[] { + return segments.map((chosenSegment) => ({ + category: chosenSegment.category, + actionType: chosenSegment.actionType, + segment: [chosenSegment.startTime, chosenSegment.endTime], + UUID: chosenSegment.UUID, + locked: chosenSegment.locked, + votes: chosenSegment.votes, + videoDuration: chosenSegment.videoDuration, + userID: chosenSegment.userID, + description: chosenSegment.description + })); +} + +async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise { + try { + const segments: DBSegment[] = await getSegmentsFromDBByVideoID(videoID, service); + console.log("dbSegments", segments); + return chooseSegment(segments); + } catch (err) { + if (err) { + Logger.error(err as string); + return null; + } + } +} + +async function getLabelsbyHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise> { + const segments: SBRecord = {}; + + try { + type SegmentWithHashPerVideoID = SBRecord; + + const segmentPerVideoID: SegmentWithHashPerVideoID = (await getSegmentsFromDBByHash(hashedVideoIDPrefix, service)) + .reduce((acc: SegmentWithHashPerVideoID, segment: DBSegment) => { + acc[segment.videoID] = acc[segment.videoID] || { + hash: segment.hashedVideoID, + segments: [] + }; + + acc[segment.videoID].segments ??= []; + acc[segment.videoID].segments.push(segment); + + return acc; + }, {}); + + for (const [videoID, videoData] of Object.entries(segmentPerVideoID)) { + const data: VideoData = { + hash: videoData.hash, + segments: chooseSegment(videoData.segments), + }; + + if (data.segments.length > 0) { + segments[videoID] = data; + } + } + + return segments; + } catch (err) { + Logger.error(err as string); + return null; + } +} + +async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise { + const fetchFromDB = () => db + .prepare( + "all", + `SELECT "startTime", "endTime", "videoID", "votes", "locked", "UUID", "userID", "category", "actionType", "hashedVideoID", "description" FROM "sponsorTimes" + WHERE "hashedVideoID" LIKE ? AND "service" = ? AND actionType = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, + [`${hashedVideoIDPrefix}%`, service] + ) as Promise; + + if (hashedVideoIDPrefix.length === 4) { + return await QueryCacher.get(fetchFromDB, videoLabelsHashKey(hashedVideoIDPrefix, service)); + } + + return await fetchFromDB(); +} + +async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): Promise { + const fetchFromDB = () => db + .prepare( + "all", + `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "description" FROM "sponsorTimes" + WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 AND "shadowHidden" = 0`, + [videoID, service] + ) as Promise; + + return await QueryCacher.get(fetchFromDB, videoLabelsKey(videoID, service)); +} + +function chooseSegment(choices: T[]): Segment[] { + // filter out -2 segments + choices = choices.filter((segment) => segment.votes > -2); + const results = []; + // trivial decisions + if (choices.length === 0) { + return []; + } else if (choices.length === 1) { + return transformDBSegments(choices); + } + // if locked, only choose from locked + const locked = choices.filter((segment) => segment.locked); + if (locked.length > 0) { + choices = locked; + } + //no need to filter, just one label + if (choices.length === 1) { + return transformDBSegments(choices); + } + // sponsor > exclusive > selfpromo + const sponsorResult = choices.find((segment) => segment.category === "sponsor"); + const eaResult = choices.find((segment) => segment.category === "exclusive_access"); + const selfpromoResult = choices.find((segment) => segment.category === "selfpromo"); + if (sponsorResult) { + results.push(sponsorResult); + } else if (eaResult) { + results.push(eaResult); + } else if (selfpromoResult) { + results.push(selfpromoResult); + } + console.log("chosenresults", results); + return transformDBSegments(results); +} + +async function handleGetLabel(req: Request, res: Response): Promise { + const videoID = req.query.videoID as VideoID; + if (!videoID) { + res.status(400).send("videoID not specified"); + return false; + } + + const service = getService(req.query.service, req.body.service); + const segments = await getLabelsByVideoID(videoID, service); + + console.log("segments", segments); + + if (!segments || segments.length === 0) { + res.sendStatus(404); + return false; + } + + return segments; +} + +async function endpoint(req: Request, res: Response): Promise { + try { + const segments = await handleGetLabel(req, res); + + // If false, res.send has already been called + if (segments) { + //send result + return res.send(segments); + } + } catch (err) { + if (err instanceof SyntaxError) { + return res.status(400).send("Categories parameter does not match format requirements."); + } else return res.sendStatus(500); + } +} + +export { + getLabelsByVideoID, + getLabelsbyHash, + endpoint +}; diff --git a/src/routes/getVideoLabelByHash.ts b/src/routes/getVideoLabelByHash.ts new file mode 100644 index 0000000..7ffc0e3 --- /dev/null +++ b/src/routes/getVideoLabelByHash.ts @@ -0,0 +1,27 @@ +import { hashPrefixTester } from "../utils/hashPrefixTester"; +import { getLabelsbyHash } from "./getVideoLabel"; +import { Request, Response } from "express"; +import { VideoIDHash, Service } from "../types/segments.model"; +import { getService } from "../utils/getService"; + +export async function getVideoLabelsByHash(req: Request, res: Response): Promise { + let hashPrefix = req.params.prefix as VideoIDHash; + if (!req.params.prefix || !hashPrefixTester(req.params.prefix)) { + return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix + } + hashPrefix = hashPrefix.toLowerCase() as VideoIDHash; + + const service: Service = getService(req.query.service, req.body.service); + + // Get all video id's that match hash prefix + const segments = await getLabelsbyHash(hashPrefix, service); + + if (!segments) return res.status(404).json([]); + + const output = Object.entries(segments).map(([videoID, data]) => ({ + videoID, + hash: data.hash, + segments: data.segments, + })); + return res.status(output.length === 0 ? 404 : 200).json(output); +} diff --git a/src/utils/redisKeys.ts b/src/utils/redisKeys.ts index 2da45a8..90f7343 100644 --- a/src/utils/redisKeys.ts +++ b/src/utils/redisKeys.ts @@ -36,4 +36,14 @@ export function shaHashKey(singleIter: HashedValue): string { } export const tempVIPKey = (userID: HashedUserID): string => - `vip.temp.${userID}`; \ No newline at end of file + `vip.temp.${userID}`; + +export const videoLabelsKey = (videoID: VideoID, service: Service): string => + `labels.v1.${service}.videoID.${videoID}`; + +export function videoLabelsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string { + hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 4) as VideoIDHash; + if (hashedVideoIDPrefix.length !== 4) Logger.warn(`Redis skip segment hash-prefix key is not length 4! ${hashedVideoIDPrefix}`); + + return `labels.v1.${service}.${hashedVideoIDPrefix}`; +} \ No newline at end of file diff --git a/test/cases/getVideoLabels.ts b/test/cases/getVideoLabels.ts new file mode 100644 index 0000000..7202d2c --- /dev/null +++ b/test/cases/getVideoLabels.ts @@ -0,0 +1,170 @@ +import { db } from "../../src/databases/databases"; +import assert from "assert"; +import { client } from "../utils/httpClient"; + +describe("getVideoLabels", () => { + const endpoint = "/api/videoLabels"; + before(async () => { + const query = 'INSERT INTO "sponsorTimes" ("videoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0)'; + await db.prepare("run", query, ["getLabelSponsor" , 2, 0, "label01", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelEA" , 2, 0, "label02", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelSelfpromo" , 2, 0, "label03", "labeluser", 0, "selfpromo", "full", 0, 0]); + // priority override + await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label04", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label05", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelPriority" , 2, 0, "label06", "labeluser", 0, "selfpromo", "full", 0, 0]); + // locked only + await db.prepare("run", query, ["getLabelLocked" , 2, 0, "label07", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelLocked" , 2, 0, "label08", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelLocked" , 2, 1, "label09", "labeluser", 0, "selfpromo", "full", 0, 0]); + // hidden segments + await db.prepare("run", query, ["getLabelDownvote" ,-2, 0, "label10", "labeluser", 0, "selfpromo", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHidden" ,2, 0, "label11", "labeluser", 0, "selfpromo", "full", 1, 0]); + await db.prepare("run", query, ["getLabelShadowHidden",2, 0, "label12", "labeluser", 0, "selfpromo", "full", 0, 1]); + // priority override2 + await db.prepare("run", query, ["getLabelPriority2" , -2, 0, "label13", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelPriority2" , 2, 0, "label14", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelPriority2" , 2, 0, "label15", "labeluser", 0, "selfpromo", "full", 0, 0]); + + return; + }); + + function validateLabel(result: any) { + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].segment[0], 0); + assert.strictEqual(result[0].segment[1], 0); + assert.strictEqual(result[0].actionType, "full"); + assert.strictEqual(result[0].userID, "labeluser"); + } + + const get = (videoID: string) => client.get(endpoint, { params: { videoID } }); + + it("Should be able to get sponsor only label", (done) => { + get("getLabelSponsor") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "sponsor"); + assert.strictEqual(data[0].UUID, "label01"); + assert.strictEqual(data[0].locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get exclusive access only label", (done) => { + get("getLabelEA") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "exclusive_access"); + assert.strictEqual(data[0].UUID, "label02"); + assert.strictEqual(data[0].locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get selfpromo only label", (done) => { + get("getLabelSelfpromo") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "selfpromo"); + assert.strictEqual(data[0].UUID, "label03"); + assert.strictEqual(data[0].locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get only sponsor if multiple segments exist", (done) => { + get("getLabelPriority") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "sponsor"); + assert.strictEqual(data[0].UUID, "label04"); + assert.strictEqual(data[0].locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should override priority if locked", (done) => { + get("getLabelLocked") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "selfpromo"); + assert.strictEqual(data[0].UUID, "label09"); + assert.strictEqual(data[0].locked, 1); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get highest priority category", (done) => { + get("getLabelPriority2") + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data); + assert.strictEqual(data[0].category, "exclusive_access"); + assert.strictEqual(data[0].UUID, "label14"); + assert.strictEqual(data[0].locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are downvoted", (done) => { + get("getLabelDownvote") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are hidden", (done) => { + get("getLabelHidden") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are shadowhidden", (done) => { + get("getLabelShadowHidden") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if no segment found", (done) => { + get("notarealvideo") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get 400 if no videoID passed in", (done) => { + client.get(endpoint) + .then(res => { + assert.strictEqual(res.status, 400); + done(); + }) + .catch(err => done(err)); + }); +}); From 267320bc563c04395c7e0c57a89db901f5ea8bcc Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 31 Mar 2022 03:47:25 -0400 Subject: [PATCH 2/8] add filler and exclusive_access to category enum --- src/types/segments.model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index 5edb3e0..c4aa23e 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -5,7 +5,7 @@ import { HashedUserID, UserID } from "./user.model"; export type SegmentUUID = string & { __segmentUUIDBrand: unknown }; export type VideoID = string & { __videoIDBrand: unknown }; export type VideoDuration = number & { __videoDurationBrand: unknown }; -export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight" | "chapter") & { __categoryBrand: unknown }; +export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight" | "chapter" | "filler" | "exclusive_access") & { __categoryBrand: unknown }; export type VideoIDHash = VideoID & HashedValue; export type IPAddress = string & { __ipAddressBrand: unknown }; export type HashedIP = IPAddress & HashedValue; From ed560425a08ab80350e3096ce776409cb94965bf Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 31 Mar 2022 14:05:07 -0400 Subject: [PATCH 3/8] removed logging, fixed labelByHash test cases --- src/routes/getVideoLabel.ts | 8 +- test/cases/getVideoLabelByHash.ts | 184 ++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 6 deletions(-) create mode 100644 test/cases/getVideoLabelByHash.ts diff --git a/src/routes/getVideoLabel.ts b/src/routes/getVideoLabel.ts index fc98c5b..48a2504 100644 --- a/src/routes/getVideoLabel.ts +++ b/src/routes/getVideoLabel.ts @@ -24,7 +24,6 @@ function transformDBSegments(segments: DBSegment[]): Segment[] { async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise { try { const segments: DBSegment[] = await getSegmentsFromDBByVideoID(videoID, service); - console.log("dbSegments", segments); return chooseSegment(segments); } catch (err) { if (err) { @@ -76,7 +75,7 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service .prepare( "all", `SELECT "startTime", "endTime", "videoID", "votes", "locked", "UUID", "userID", "category", "actionType", "hashedVideoID", "description" FROM "sponsorTimes" - WHERE "hashedVideoID" LIKE ? AND "service" = ? AND actionType = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, + WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, [`${hashedVideoIDPrefix}%`, service] ) as Promise; @@ -92,7 +91,7 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P .prepare( "all", `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "description" FROM "sponsorTimes" - WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 AND "shadowHidden" = 0`, + WHERE "videoID" = ? AND "service" = ? AND "actionType" = 'full' AND "hidden" = 0 AND "shadowHidden" = 0`, [videoID, service] ) as Promise; @@ -129,7 +128,6 @@ function chooseSegment(choices: T[]): Segment[] { } else if (selfpromoResult) { results.push(selfpromoResult); } - console.log("chosenresults", results); return transformDBSegments(results); } @@ -143,8 +141,6 @@ async function handleGetLabel(req: Request, res: Response): Promise { + const endpoint = "/api/videoLabels"; + before(async () => { + const query = 'INSERT INTO "sponsorTimes" ("videoID", "hashedVideoID", "votes", "locked", "UUID", "userID", "timeSubmitted", "category", "actionType", "hidden", "shadowHidden", "startTime", "endTime", "views") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0)'; + await db.prepare("run", query, ["getLabelHashSponsor" , getHash("getLabelHashSponsor", 1) , 2, 0, "labelhash01", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashEA" , getHash("getLabelHashEA", 1) , 2, 0, "labelhash02", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashSelfpromo" , getHash("getLabelHashSelfpromo", 1) , 2, 0, "labelhash03", "labeluser", 0, "selfpromo", "full", 0, 0]); + // priority override + await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash04", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash05", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashPriority" , getHash("getLabelHashPriority", 1) , 2, 0, "labelhash06", "labeluser", 0, "selfpromo", "full", 0, 0]); + // locked only + await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash07", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 0, "labelhash08", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashLocked" , getHash("getLabelHashLocked", 1) , 2, 1, "labelhash09", "labeluser", 0, "selfpromo", "full", 0, 0]); + // hidden segments + await db.prepare("run", query, ["getLabelHashDownvote" , getHash("getLabelHashDownvote", 1) , -2, 0, "labelhash10", "labeluser", 0, "selfpromo", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashHidden" , getHash("getLabelHashHidden", 1) , 2, 0, "labelhash11", "labeluser", 0, "selfpromo", "full", 1, 0]); + await db.prepare("run", query, ["getLabelHashShHidden" , getHash("getLabelHashShHidden", 1) , 2, 0, "labelhash12", "labeluser", 0, "selfpromo", "full", 0, 1]); + // priority override2 + await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , -2, 0, "labelhash13", "labeluser", 0, "sponsor", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash14", "labeluser", 0, "exclusive_access", "full", 0, 0]); + await db.prepare("run", query, ["getLabelHashPriority2" , getHash("getLabelHashPriority2", 1) , 2, 0, "labelhash15", "labeluser", 0, "selfpromo", "full", 0, 0]); + + return; + }); + + function validateLabel(data: any, videoID: string) { + assert.strictEqual(data[0].videoID, videoID); + assert.strictEqual(data[0].segments.length, 1); + assert.strictEqual(data[0].segments[0].segment[0], 0); + assert.strictEqual(data[0].segments[0].segment[1], 0); + assert.strictEqual(data[0].segments[0].actionType, "full"); + assert.strictEqual(data[0].segments[0].userID, "labeluser"); + } + + const get = (videoID: string) => client.get(`${endpoint}/${getHash(videoID, 1).substring(0, 4)}`); + + it("Should be able to get sponsor only label", (done) => { + const videoID = "getLabelHashSponsor"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "sponsor"); + assert.strictEqual(result.UUID, "labelhash01"); + assert.strictEqual(result.locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get exclusive access only label", (done) => { + const videoID = "getLabelHashEA"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "exclusive_access"); + assert.strictEqual(result.UUID, "labelhash02"); + assert.strictEqual(result.locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get selfpromo only label", (done) => { + const videoID = "getLabelHashSelfpromo"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "selfpromo"); + assert.strictEqual(result.UUID, "labelhash03"); + assert.strictEqual(result.locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get only sponsor if multiple segments exist", (done) => { + const videoID = "getLabelHashPriority"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "sponsor"); + assert.strictEqual(result.UUID, "labelhash04"); + assert.strictEqual(result.locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should override priority if locked", (done) => { + const videoID = "getLabelHashLocked"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "selfpromo"); + assert.strictEqual(result.UUID, "labelhash09"); + assert.strictEqual(result.locked, 1); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get highest priority category", (done) => { + const videoID = "getLabelHashPriority2"; + get(videoID) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + validateLabel(data, videoID); + const result = data[0].segments[0]; + assert.strictEqual(result.category, "exclusive_access"); + assert.strictEqual(result.UUID, "labelhash14"); + assert.strictEqual(result.locked, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are downvoted", (done) => { + get("getLabelHashDownvote") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are hidden", (done) => { + get("getLabelHashHidden") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if all submissions are shadowhidden", (done) => { + get("getLabelHashShHidden") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 404 if no segment found", (done) => { + get("notarealvideo") + .then(res => { + assert.strictEqual(res.status, 404); + done(); + }) + .catch(err => done(err)); + }); + + it("Should get 400 if no videoID passed in", (done) => { + client.get(endpoint) + .then(res => { + assert.strictEqual(res.status, 400); + done(); + }) + .catch(err => done(err)); + }); +}); From 2fb7f4faa66d2ec24ac79a56b06add3bfbb92bef Mon Sep 17 00:00:00 2001 From: "Michael M. Chang" Date: Thu, 31 Mar 2022 14:05:52 -0400 Subject: [PATCH 4/8] consistent function naming Co-authored-by: Ajay Ramachandran --- src/routes/getVideoLabel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getVideoLabel.ts b/src/routes/getVideoLabel.ts index 48a2504..1ddc98b 100644 --- a/src/routes/getVideoLabel.ts +++ b/src/routes/getVideoLabel.ts @@ -33,7 +33,7 @@ async function getLabelsByVideoID(videoID: VideoID, service: Service): Promise> { +async function getLabelsByHash(hashedVideoIDPrefix: VideoIDHash, service: Service): Promise> { const segments: SBRecord = {}; try { From 65a661ca4d6b659c7c6b9dea8e7db0797670bee4 Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 31 Mar 2022 14:19:06 -0400 Subject: [PATCH 5/8] fix naming regression, simplify findCategory --- src/routes/getVideoLabel.ts | 17 ++++++----------- src/routes/getVideoLabelByHash.ts | 4 ++-- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/routes/getVideoLabel.ts b/src/routes/getVideoLabel.ts index 1ddc98b..02ba7c5 100644 --- a/src/routes/getVideoLabel.ts +++ b/src/routes/getVideoLabel.ts @@ -118,16 +118,11 @@ function chooseSegment(choices: T[]): Segment[] { return transformDBSegments(choices); } // sponsor > exclusive > selfpromo - const sponsorResult = choices.find((segment) => segment.category === "sponsor"); - const eaResult = choices.find((segment) => segment.category === "exclusive_access"); - const selfpromoResult = choices.find((segment) => segment.category === "selfpromo"); - if (sponsorResult) { - results.push(sponsorResult); - } else if (eaResult) { - results.push(eaResult); - } else if (selfpromoResult) { - results.push(selfpromoResult); - } + const findCategory = (category: string) => choices.find((segment) => segment.category === category); + + const categoryResult = findCategory("sponsor") ?? findCategory("exclusive_access") ?? findCategory("selfpromo"); + if (categoryResult) results.push(categoryResult); + return transformDBSegments(results); } @@ -167,6 +162,6 @@ async function endpoint(req: Request, res: Response): Promise { export { getLabelsByVideoID, - getLabelsbyHash, + getLabelsByHash, endpoint }; diff --git a/src/routes/getVideoLabelByHash.ts b/src/routes/getVideoLabelByHash.ts index 7ffc0e3..e3a3615 100644 --- a/src/routes/getVideoLabelByHash.ts +++ b/src/routes/getVideoLabelByHash.ts @@ -1,5 +1,5 @@ import { hashPrefixTester } from "../utils/hashPrefixTester"; -import { getLabelsbyHash } from "./getVideoLabel"; +import { getLabelsByHash } from "./getVideoLabel"; import { Request, Response } from "express"; import { VideoIDHash, Service } from "../types/segments.model"; import { getService } from "../utils/getService"; @@ -14,7 +14,7 @@ export async function getVideoLabelsByHash(req: Request, res: Response): Promise const service: Service = getService(req.query.service, req.body.service); // Get all video id's that match hash prefix - const segments = await getLabelsbyHash(hashPrefix, service); + const segments = await getLabelsByHash(hashPrefix, service); if (!segments) return res.status(404).json([]); From 7f92ac961d60dcc713550962c32b6b0d62acc38d Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 28 Oct 2022 16:42:56 -0400 Subject: [PATCH 6/8] clear label cache --- src/utils/queryCacher.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index cb1faeb..b1f3192 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -1,6 +1,6 @@ import redis from "../utils/redis"; import { Logger } from "../utils/logger"; -import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey } from "./redisKeys"; +import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsKey } from "./redisKeys"; import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; import { config } from "../config"; @@ -81,6 +81,7 @@ function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoID redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); + redis.del(videoLabelsKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)).catch((err) => Logger.error(err)); } } From cc6c5980a1453ccdd6dc34da906ef48b385c6d4b Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 28 Oct 2022 16:49:10 -0400 Subject: [PATCH 7/8] videoLabelsHashKey --- src/utils/queryCacher.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index b1f3192..463b474 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -1,6 +1,6 @@ import redis from "../utils/redis"; import { Logger } from "../utils/logger"; -import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsKey } from "./redisKeys"; +import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsHashKey } from "./redisKeys"; import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; import { config } from "../config"; @@ -81,7 +81,7 @@ function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoID redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); - redis.del(videoLabelsKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); + redis.del(videoLabelsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)).catch((err) => Logger.error(err)); } } From db71af480d81529c2035f95f1d6c935bbae3e354 Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 28 Oct 2022 16:53:04 -0400 Subject: [PATCH 8/8] clear full video labels --- src/utils/queryCacher.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 463b474..1b3f6a1 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -1,6 +1,6 @@ import redis from "../utils/redis"; import { Logger } from "../utils/logger"; -import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsHashKey } from "./redisKeys"; +import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey, videoLabelsKey, videoLabelsHashKey } from "./redisKeys"; import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; import { config } from "../config"; @@ -81,6 +81,7 @@ function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoID redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); + redis.del(videoLabelsKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); redis.del(videoLabelsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)).catch((err) => Logger.error(err)); }