From 6919b5433b3077e1bd1aa4bdf163fde7a3c737cb Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Sat, 6 Nov 2021 22:43:03 -0400 Subject: [PATCH] Add suggested chapter names --- databases/_sponsorTimes_indexes.sql | 5 +++ src/app.ts | 4 ++ src/databases/Postgres.ts | 2 +- src/routes/getChapterNames.ts | 46 ++++++++++++++++++++++ test/cases/getChapterNames.ts | 61 +++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/routes/getChapterNames.ts create mode 100644 test/cases/getChapterNames.ts diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index 968558f..4a353d0 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -25,6 +25,11 @@ CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" ("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, category COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST) TABLESPACE pg_default; +CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin" + ON public."sponsorTimes" USING gin + ("description" COLLATE pg_catalog."default" gin_trgm_ops, category COLLATE pg_catalog."default" gin_trgm_ops) + TABLESPACE pg_default; + -- userNames CREATE INDEX IF NOT EXISTS "userNames_userID" diff --git a/src/app.ts b/src/app.ts index 974805e..fbb7d2c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -42,6 +42,7 @@ import { getUserStats } from "./routes/getUserStats"; import ExpressPromiseRouter from "express-promise-router"; import { Server } from "http"; import { youtubeApiProxy } from "./routes/youtubeApiProxy"; +import { getChapterNames } from "./routes/getChapterNames"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -172,6 +173,9 @@ function setupRoutes(router: Router) { // get all segments that match a search router.get("/api/searchSegments", getSearchSegments); + // autocomplete chapter names + router.get("/api/chapterNames", getChapterNames); + // get status router.get("/api/status/:value", getStatus); router.get("/api/status", getStatus); diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 5c2b5c5..79d2adb 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -68,7 +68,7 @@ export class Postgres implements IDatabase { } case "all": { const values = queryResult.rows; - Logger.debug(`result (postgres): ${values}`); + Logger.debug(`result (postgres): ${JSON.stringify(values)}`); return values; } case "run": { diff --git a/src/routes/getChapterNames.ts b/src/routes/getChapterNames.ts new file mode 100644 index 0000000..1b04e5b --- /dev/null +++ b/src/routes/getChapterNames.ts @@ -0,0 +1,46 @@ +import { Logger } from "../utils/logger"; +import { Request, Response } from "express"; +import { db } from "../databases/databases"; +import { Postgres } from "../databases/Postgres"; + +export async function getChapterNames(req: Request, res: Response): Promise { + const description = req.query.description as string; + const channelID = req.query.channelID as string; + + if (!description || typeof(description) !== "string" + || !channelID || typeof(channelID) !== "string") { + return res.sendStatus(400); + } + + if (!(db instanceof Postgres)) { + return res.sendStatus(500).json({ + message: "Not supported on this instance" + }); + } + + try { + const descriptions = await db.prepare("all", ` + SELECT "description" + FROM "sponsorTimes" + WHERE ("votes" > 0 OR ("views" > 100 AND "votes" >= 0)) AND "videoID" IN ( + SELECT "videoID" + FROM "videoInfo" + WHERE "channelID" = ? + ) AND "description" != '' + GROUP BY "description" + ORDER BY SUM("votes"), similarity("description", ?) DESC + LIMIT 5;` + , [channelID, description]) as { description: string }[]; + + if (descriptions?.length > 0) { + return res.status(200).json(descriptions.map(d => ({ + description: d.description + }))); + } + } catch (err) { + Logger.error(err as string); + return res.sendStatus(500); + } + + return res.status(404).json([]); +} diff --git a/test/cases/getChapterNames.ts b/test/cases/getChapterNames.ts new file mode 100644 index 0000000..a1ff03e --- /dev/null +++ b/test/cases/getChapterNames.ts @@ -0,0 +1,61 @@ +import assert from "assert"; +import { db } from "../../src/databases/databases"; +import { Postgres } from "../../src/databases/Postgres"; +import { client } from "../utils/httpClient"; +import { partialDeepEquals } from "../utils/partialDeepEquals"; + +// Only works with Postgres +if (db instanceof Postgres) { + + describe("getChapterNames", function () { + const endpoint = "/api/chapterNames"; + + const chapterNamesVid1 = "chapterNamesVid"; + const chapterChannelID = "chapterChannelID"; + + before(async () => { + const query = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "hidden", "shadowHidden", "description") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + await db.prepare("run", query, [chapterNamesVid1, 60, 80, 2, 0, "chapterVid-1", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Weird name"]); + await db.prepare("run", query, [chapterNamesVid1, 70, 75, 2, 0, "chapterVid-2", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "A different one"]); + await db.prepare("run", query, [chapterNamesVid1, 71, 76, 2, 0, "chapterVid-3", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Something else"]); + + await db.prepare("run", `INSERT INTO "videoInfo" ("videoID", "channelID") + SELECT ?, ?`, [ + chapterNamesVid1, chapterChannelID + ]); + }); + + it("Search for 'weird'", async () => { + const result = await client.get(`${endpoint}?description=weird&channelID=${chapterChannelID}`); + const expected = [{ + description: "Weird name", + }]; + + assert.strictEqual(result.status, 200); + assert.strictEqual(result.data.length, 3); + assert.ok(partialDeepEquals(result.data, expected)); + }); + + it("Search for 'different'", async () => { + const result = await client.get(`${endpoint}?description=different&channelID=${chapterChannelID}`); + const expected = [{ + description: "A different one", + }]; + + assert.strictEqual(result.status, 200); + assert.strictEqual(result.data.length, 3); + assert.ok(partialDeepEquals(result.data, expected)); + }); + + it("Search for 'something'", async () => { + const result = await client.get(`${endpoint}?description=something&channelID=${chapterChannelID}`); + const expected = [{ + description: "Something else", + }]; + + assert.strictEqual(result.status, 200); + assert.strictEqual(result.data.length, 3); + assert.ok(partialDeepEquals(result.data, expected)); + }); + }); +} \ No newline at end of file