Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing Request Type #399

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 3 additions & 2 deletions src/routes/addUnlistedVideo.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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<Response> {
export async function addUnlistedVideo(req: APIRequest, res: Response): Promise<Response> {
const {
body: {
videoID = null,
Expand Down
14 changes: 4 additions & 10 deletions src/routes/addUserAsVIP.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {

export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Promise<Response> {
const { query: { userID, adminUserID } } = req;

const enabled = req.query?.enabled === "true";
Expand All @@ -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
Expand Down
15 changes: 3 additions & 12 deletions src/routes/deleteLockCategories.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
export async function deleteLockCategoriesEndpoint(req: APIRequest, res: Response): Promise<Response> {
// Collect user input data
const {
body: {
Expand Down
7 changes: 4 additions & 3 deletions src/routes/dumpDatabase.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -95,7 +96,7 @@ function removeOutdatedDumps(exportPath: string): Promise<void> {
});
}

export default async function dumpDatabase(req: Request, res: Response, showPage: boolean): Promise<void> {
export default async function dumpDatabase(_req: APIRequest, res: Response, showPage: boolean): Promise<void> {
if (!config?.dumpDatabase?.enabled) {
res.status(404).send("Database dump is disabled");
return;
Expand Down Expand Up @@ -170,7 +171,7 @@ async function getDbVersion(): Promise<number> {
return row.value;
}

export async function redirectLink(req: Request, res: Response): Promise<void> {
export async function redirectLink(req: APIRequest, res: Response): Promise<void> {
if (!config?.dumpDatabase?.enabled) {
res.status(404).send("Database dump is disabled");
return;
Expand Down
5 changes: 3 additions & 2 deletions src/routes/getDaysSavedFormatted.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
export async function getDaysSavedFormatted(_req: APIRequest, res: Response): Promise<Response> {
const row = await db.prepare("get", 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []);

if (row !== undefined) {
Expand Down
13 changes: 8 additions & 5 deletions src/routes/getIsUserVIP.ts
Original file line number Diff line number Diff line change
@@ -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<Response> {
const userID = req.query.userID as UserID;
export async function getIsUserVIP(req: APIRequest, res: Response): Promise<Response> {
const userID = req.query.userID;

if (userID == undefined) {
//invalid request
Expand All @@ -17,12 +18,14 @@ export async function getIsUserVIP(req: Request, res: Response): Promise<Respons

try {
const vipState = await isUserVIP(hashedUserID);

return res.status(200).json({
hashedUserID: hashedUserID,
vip: vipState,
});
} catch (err) {
Logger.error(err as string);
return res.sendStatus(500);
}

return res.sendStatus(500);
}
19 changes: 12 additions & 7 deletions src/routes/getLockCategories.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { db } from "../databases/databases";
import { Logger } from "../utils/logger";
import { Request, Response } from "express";
import { Category, VideoID } from "../types/segments.model";
import { Response } from "express";
import { Category } from "../types/segments.model";
import { getService } from "../utils/getService";
import { APIRequest } from "../types/APIRequest";

export async function getLockCategories(req: Request, res: Response): Promise<Response> {
const videoID = req.query.videoID as VideoID;
const service = getService(req.query.service as string);
export async function getLockCategories(req: APIRequest, res: Response): Promise<Response> {
const { query: { videoID, service } } = req;
const qualifiedServiceName = getService(service);

if (videoID == undefined) {
//invalid request
Expand All @@ -15,19 +16,23 @@ export async function getLockCategories(req: Request, res: Response): Promise<Re

try {
// Get existing lock categories markers
const row = await db.prepare("all", 'SELECT "category", "reason" from "lockCategories" where "videoID" = ? AND "service" = ?', [videoID, service]) as {category: Category, reason: string}[];
const row = await db.prepare("all", 'SELECT "category", "reason" from "lockCategories" where "videoID" = ? AND "service" = ?', [videoID, qualifiedServiceName]) as {category: Category, reason: string}[];
// map categories to array in JS becaues of SQL incompatibilities
const categories = row.map(item => 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);
}
}
54 changes: 28 additions & 26 deletions src/routes/getLockCategoriesByHash.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,39 +12,47 @@ interface DBLock {
reason: string,
}

const mergeLocks = (source: DBLock[]) => {
const dest: LockResultByHash[] = [];
for (const obj of source) {
interface LockResultByHash extends Omit<DBLock, "category"> {
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) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

solving in O(1) instead of O(n^2)

// 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<Response> {
let hashPrefix = req.params.prefix as VideoIDHash;
export async function getLockCategoriesByHash(req: APIRequest, res: Response): Promise<Response> {
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) {
Expand Down
26 changes: 26 additions & 0 deletions src/types/APIRequest.ts
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adminUserID is of type UserID

enabled: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabled is type integer

generate: "true" | "false";
service: "youtube" | "vimeo";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

services are in src/types/segments.model.s

},
params: {
prefix: VideoIDHash;
},
body: {
videoID: null | VideoID,
year: number,
views: number,
channelID: string,
service: string,
categories: Category[];
userID: UserID | HashedUserID;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userID retuned should always be HashedUserID

}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplified interface for what the request actually looks like.

Copy link
Contributor

@mchangrh mchangrh Oct 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adminID is always of type userID

** will comment on file itself