diff --git a/src/app.ts b/src/app.ts index 974805ea..b767e6c7 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 { APIRequest } from "./types/APIRequest"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -183,8 +184,8 @@ function setupRoutes(router: Router) { router.get("/api/lockReason", getLockReason); if (config.postgres) { - router.get("/database", (req, res) => dumpDatabase(req, res, true)); - router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); + router.get("/database", (req, res) => dumpDatabase(req as APIRequest, res, true)); + router.get("/database.json", (req, res) => dumpDatabase(req as APIRequest, res, false)); router.get("/database/*", redirectLink); } else { router.get("/database.db", function (req: Request, res: Response) { diff --git a/src/routes/addUnlistedVideo.ts b/src/routes/addUnlistedVideo.ts index d66e36cf..c1f0eb05 100644 --- a/src/routes/addUnlistedVideo.ts +++ b/src/routes/addUnlistedVideo.ts @@ -1,5 +1,6 @@ -import { Request, Response } from "express"; +import { Response } from "express"; import { db } from "../databases/databases"; +import { APIRequest } from "../types/APIRequest"; import { getService } from "../utils/getService"; import { Logger } from "../utils/logger"; @@ -10,7 +11,7 @@ import { Logger } from "../utils/logger"; * https://support.google.com/youtube/answer/9230970 */ -export async function addUnlistedVideo(req: Request, res: Response): Promise { +export async function addUnlistedVideo(req: APIRequest, res: Response): Promise { const { body: { videoID = null, diff --git a/src/routes/addUserAsVIP.ts b/src/routes/addUserAsVIP.ts index 3d944905..1b955acf 100644 --- a/src/routes/addUserAsVIP.ts +++ b/src/routes/addUserAsVIP.ts @@ -1,19 +1,13 @@ import { getHash } from "../utils/getHash"; import { db } from "../databases/databases"; import { config } from "../config"; -import { Request, Response } from "express"; +import { Response } from "express"; import { isUserVIP } from "../utils/isUserVIP"; import { HashedUserID } from "../types/user.model"; +import { APIRequest } from "../types/APIRequest"; -interface AddUserAsVIPRequest extends Request { - query: { - userID: HashedUserID; - adminUserID: string; - enabled: string; - } -} +export async function addUserAsVIP(req: APIRequest, res: Response): Promise { -export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Promise { const { query: { userID, adminUserID } } = req; const enabled = req.query?.enabled === "true"; @@ -32,7 +26,7 @@ export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Pro } // check to see if this user is already a vip - const userIsVIP = await isUserVIP(userID); + const userIsVIP = await isUserVIP(userID as HashedUserID); if (enabled && !userIsVIP) { // add them to the vip list diff --git a/src/routes/deleteLockCategories.ts b/src/routes/deleteLockCategories.ts index e98ed23c..cd70101e 100644 --- a/src/routes/deleteLockCategories.ts +++ b/src/routes/deleteLockCategories.ts @@ -1,21 +1,12 @@ -import { Request, Response } from "express"; +import { Response } from "express"; import { isUserVIP } from "../utils/isUserVIP"; import { getHash } from "../utils/getHash"; import { db } from "../databases/databases"; import { Category, Service, VideoID } from "../types/segments.model"; -import { UserID } from "../types/user.model"; import { getService } from "../utils/getService"; +import { APIRequest } from "../types/APIRequest"; -interface DeleteLockCategoriesRequest extends Request { - body: { - categories: Category[]; - service: string; - userID: UserID; - videoID: VideoID; - }; -} - -export async function deleteLockCategoriesEndpoint(req: DeleteLockCategoriesRequest, res: Response): Promise { +export async function deleteLockCategoriesEndpoint(req: APIRequest, res: Response): Promise { // Collect user input data const { body: { diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index f03446fa..bb7b88ca 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -1,10 +1,11 @@ import { db } from "../databases/databases"; import { Logger } from "../utils/logger"; -import { Request, Response } from "express"; +import { Response } from "express"; import { config } from "../config"; import util from "util"; import fs from "fs"; import path from "path"; +import { APIRequest } from "../types/APIRequest"; const unlink = util.promisify(fs.unlink); const ONE_MINUTE = 1000 * 60; @@ -95,7 +96,7 @@ function removeOutdatedDumps(exportPath: string): Promise { }); } -export default async function dumpDatabase(req: Request, res: Response, showPage: boolean): Promise { +export default async function dumpDatabase(_req: APIRequest, res: Response, showPage: boolean): Promise { if (!config?.dumpDatabase?.enabled) { res.status(404).send("Database dump is disabled"); return; @@ -170,7 +171,7 @@ async function getDbVersion(): Promise { return row.value; } -export async function redirectLink(req: Request, res: Response): Promise { +export async function redirectLink(req: APIRequest, res: Response): Promise { if (!config?.dumpDatabase?.enabled) { res.status(404).send("Database dump is disabled"); return; diff --git a/src/routes/getDaysSavedFormatted.ts b/src/routes/getDaysSavedFormatted.ts index 61046e38..2a1719b7 100644 --- a/src/routes/getDaysSavedFormatted.ts +++ b/src/routes/getDaysSavedFormatted.ts @@ -1,7 +1,8 @@ import { db } from "../databases/databases"; -import { Request, Response } from "express"; +import { Response } from "express"; +import { APIRequest } from "../types/APIRequest"; -export async function getDaysSavedFormatted(req: Request, res: Response): Promise { +export async function getDaysSavedFormatted(_req: APIRequest, res: Response): Promise { const row = await db.prepare("get", 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []); if (row !== undefined) { diff --git a/src/routes/getIsUserVIP.ts b/src/routes/getIsUserVIP.ts index 7b2d5233..f6e74ce3 100644 --- a/src/routes/getIsUserVIP.ts +++ b/src/routes/getIsUserVIP.ts @@ -1,11 +1,12 @@ import { Logger } from "../utils/logger"; import { getHash } from "../utils/getHash"; import { isUserVIP } from "../utils/isUserVIP"; -import { Request, Response } from "express"; -import { HashedUserID, UserID } from "../types/user.model"; +import { Response } from "express"; +import { HashedUserID } from "../types/user.model"; +import { APIRequest } from "../types/APIRequest"; -export async function getIsUserVIP(req: Request, res: Response): Promise { - const userID = req.query.userID as UserID; +export async function getIsUserVIP(req: APIRequest, res: Response): Promise { + const userID = req.query.userID; if (userID == undefined) { //invalid request @@ -17,12 +18,14 @@ export async function getIsUserVIP(req: Request, res: Response): Promise { - const videoID = req.query.videoID as VideoID; - const service = getService(req.query.service as string); +export async function getLockCategories(req: APIRequest, res: Response): Promise { + const { query: { videoID, service } } = req; + const qualifiedServiceName = getService(service); if (videoID == undefined) { //invalid request @@ -15,19 +16,23 @@ export async function getLockCategories(req: Request, res: Response): Promise item.category); - if (categories.length === 0 || !categories[0]) return res.sendStatus(404); + if (categories.length === 0) { + return res.sendStatus(404); + } // Get longest lock reason const reason = row.map(item => item.reason) .reduce((a,b) => (a.length > b.length) ? a : b); + return res.send({ reason, categories }); } catch (err) { Logger.error(err as string); + return res.sendStatus(500); } } diff --git a/src/routes/getLockCategoriesByHash.ts b/src/routes/getLockCategoriesByHash.ts index 6ff6252f..1eae7219 100644 --- a/src/routes/getLockCategoriesByHash.ts +++ b/src/routes/getLockCategoriesByHash.ts @@ -1,15 +1,9 @@ import { db } from "../databases/databases"; import { Logger } from "../utils/logger"; -import { Request, Response } from "express"; +import { Response } from "express"; import { hashPrefixTester } from "../utils/hashPrefixTester"; import { Category, VideoID, VideoIDHash } from "../types/segments.model"; - -interface LockResultByHash { - videoID: VideoID, - hash: VideoIDHash, - reason: string, - categories: Category[] -} +import { APIRequest } from "../types/APIRequest"; interface DBLock { videoID: VideoID, @@ -18,39 +12,47 @@ interface DBLock { reason: string, } -const mergeLocks = (source: DBLock[]) => { - const dest: LockResultByHash[] = []; - for (const obj of source) { +interface LockResultByHash extends Omit { + categories: Category[]; +} + +const mergeLocks = (source: DBLock[]) : LockResultByHash[]=> { + const dest: { [videoID: VideoID]: LockResultByHash } = {}; + for (const { videoID, reason, hash, category } of source) { // videoID already exists - const destMatch = dest.find(s => s.videoID === obj.videoID); - if (destMatch) { + if (videoID in dest) { // override longer reason - if (obj.reason?.length > destMatch.reason?.length) destMatch.reason = obj.reason; + const destMatch = dest[videoID]; + destMatch.reason = (reason?.length > destMatch.reason?.length) ? reason : destMatch.reason; // push to categories - destMatch.categories.push(obj.category); + destMatch.categories.push(category); } else { - dest.push({ - videoID: obj.videoID, - hash: obj.hash, - reason: obj.reason, - categories: [obj.category] - }); + dest[videoID] = { + videoID, + hash, + reason, + categories: [category] + }; } } - return dest; + + return Object.values(dest); }; -export async function getLockCategoriesByHash(req: Request, res: Response): Promise { - let hashPrefix = req.params.prefix as VideoIDHash; +export async function getLockCategoriesByHash(req: APIRequest, res: Response): Promise { + let { params: { prefix: hashPrefix } }= req; if (!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; + hashPrefix = hashPrefix.toLowerCase() as APIRequest["params"]["prefix"]; try { // Get existing lock categories markers const lockedRows = await db.prepare("all", 'SELECT "videoID", "hashedVideoID" as "hash", "category", "reason" from "lockCategories" where "hashedVideoID" LIKE ?', [`${hashPrefix}%`]) as DBLock[]; - if (lockedRows.length === 0 || !lockedRows[0]) return res.sendStatus(404); + if (lockedRows.length === 0) { + return res.sendStatus(404); + } + // merge all locks return res.send(mergeLocks(lockedRows)); } catch (err) { diff --git a/src/types/APIRequest.ts b/src/types/APIRequest.ts new file mode 100644 index 00000000..04d5a9dc --- /dev/null +++ b/src/types/APIRequest.ts @@ -0,0 +1,26 @@ +import { HashedUserID, UserID } from "./user.model"; +import { Request } from "express"; +import { Category, VideoID, VideoIDHash } from "./segments.model"; + +export interface APIRequest extends Request { + query: { + videoID: VideoID; + userID: UserID | HashedUserID; + adminUserID: string; + enabled: string; + generate: "true" | "false"; + service: "youtube" | "vimeo"; + }, + params: { + prefix: VideoIDHash; + }, + body: { + videoID: null | VideoID, + year: number, + views: number, + channelID: string, + service: string, + categories: Category[]; + userID: UserID | HashedUserID; + } +}