diff --git a/src/index.ts b/src/index.ts index ec44960..9a15fb2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,7 +10,11 @@ async function init() { process.on("unhandledRejection", (error: any) => { // eslint-disable-next-line no-console console.dir(error?.stack); - process.exit(1); + }); + + process.on("uncaughtExceptions", (error: any) => { + // eslint-disable-next-line no-console + console.dir(error?.stack); }); try { diff --git a/src/routes/addUserAsVIP.ts b/src/routes/addUserAsVIP.ts index 3e7f0c7..ab6cd8e 100644 --- a/src/routes/addUserAsVIP.ts +++ b/src/routes/addUserAsVIP.ts @@ -4,6 +4,7 @@ import { config } from "../config"; import { Request, Response } from "express"; import { isUserVIP } from "../utils/isUserVIP"; import { HashedUserID } from "../types/user.model"; +import { Logger } from "../utils/logger"; interface AddUserAsVIPRequest extends Request { query: { @@ -34,15 +35,21 @@ export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Pro // check to see if this user is already a vip const userIsVIP = await isUserVIP(userID); - if (enabled && !userIsVIP) { - // add them to the vip list - await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]); + try { + if (enabled && !userIsVIP) { + // add them to the vip list + await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]); + } + + if (!enabled && userIsVIP) { + //remove them from the shadow ban list + await db.prepare("run", 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]); + } + + return res.sendStatus(200); + } catch (e) { + Logger.error(e as string); + return res.sendStatus(500); } - if (!enabled && userIsVIP) { - //remove them from the shadow ban list - await db.prepare("run", 'DELETE FROM "vipUsers" WHERE "userID" = ?', [userID]); - } - - return res.sendStatus(200); } diff --git a/src/routes/deleteLockCategories.ts b/src/routes/deleteLockCategories.ts index 3340f10..c163656 100644 --- a/src/routes/deleteLockCategories.ts +++ b/src/routes/deleteLockCategories.ts @@ -6,6 +6,7 @@ import { ActionType, Category, Service, VideoID } from "../types/segments.model" import { UserID } from "../types/user.model"; import { getService } from "../utils/getService"; import { config } from "../config"; +import { Logger } from "../utils/logger"; interface DeleteLockCategoriesRequest extends Request { body: { @@ -53,7 +54,12 @@ export async function deleteLockCategoriesEndpoint(req: DeleteLockCategoriesRequ }); } - await deleteLockCategories(videoID, categories, actionTypes, getService(service)); + try { + await deleteLockCategories(videoID, categories, actionTypes, getService(service)); + } catch (e) { + Logger.error(e as string); + return res.status(500); + } return res.status(200).json({ message: `Removed lock categories entries for video ${videoID}` }); } diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index 10e21d6..dfbf006 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -164,18 +164,23 @@ export default async function dumpDatabase(req: Request, res: Response, showPage
${updateQueued ? `Update queued.` : ``} Last updated: ${lastUpdate ? new Date(lastUpdate).toUTCString() : `Unknown`}`); } else { - res.send({ - dbVersion: await getDbVersion(), - lastUpdated: lastUpdate, - updateQueued, - links: latestDumpFiles.map((item:any) => { - return { - table: item.tableName, - url: `/database/${item.tableName}.csv`, - size: item.fileSize, - }; - }), - }); + try { + res.send({ + dbVersion: await getDbVersion(), + lastUpdated: lastUpdate, + updateQueued, + links: latestDumpFiles.map((item:any) => { + return { + table: item.tableName, + url: `/database/${item.tableName}.csv`, + size: item.fileSize, + }; + }), + }); + } catch (e) { + Logger.error(e as string); + res.sendStatus(500); + } } await queueDump(); diff --git a/src/routes/getBrandingStats.ts b/src/routes/getBrandingStats.ts index 1ecd9f4..b6a9daf 100644 --- a/src/routes/getBrandingStats.ts +++ b/src/routes/getBrandingStats.ts @@ -25,20 +25,25 @@ let lastFetch: DBStatsData = { updateExtensionUsers(); export async function getBrandingStats(req: Request, res: Response): Promise { - const row = await getStats(); - lastFetch = row; + try { + const row = await getStats(); + lastFetch = row; - /* istanbul ignore if */ - if (!row) res.sendStatus(500); - const extensionUsers = chromeUsersCache + firefoxUsersCache; + /* istanbul ignore if */ + if (!row) res.sendStatus(500); + const extensionUsers = chromeUsersCache + firefoxUsersCache; - //send this result - res.send({ - userCount: row.userCount ?? 0, - activeUsers: extensionUsers, - titles: row.titles, - thumbnails: row.thumbnails, - }); + //send this result + res.send({ + userCount: row.userCount ?? 0, + activeUsers: extensionUsers, + titles: row.titles, + thumbnails: row.thumbnails, + }); + } catch (e) { + Logger.error(e as string); + res.sendStatus(500); + } } async function getStats(): Promise { diff --git a/src/routes/getDaysSavedFormatted.ts b/src/routes/getDaysSavedFormatted.ts index bd906a6..f939a93 100644 --- a/src/routes/getDaysSavedFormatted.ts +++ b/src/routes/getDaysSavedFormatted.ts @@ -1,17 +1,23 @@ import { db } from "../databases/databases"; import { Request, Response } from "express"; +import { Logger } from "../utils/logger"; export async function getDaysSavedFormatted(req: Request, res: Response): Promise { - const row = await db.prepare("get", 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []); + try { + const row = await db.prepare("get", 'SELECT SUM(("endTime" - "startTime") / 60 / 60 / 24 * "views") as "daysSaved" from "sponsorTimes" where "shadowHidden" != 1', []); - if (row !== undefined) { - //send this result - return res.send({ - daysSaved: row.daysSaved?.toFixed(2) ?? "0", - }); - } else { - return res.send({ - daysSaved: 0 - }); + if (row !== undefined) { + //send this result + return res.send({ + daysSaved: row.daysSaved?.toFixed(2) ?? "0", + }); + } else { + return res.send({ + daysSaved: 0 + }); + } + } catch (err) { + Logger.error(err as string); + return res.sendStatus(500); } } diff --git a/src/routes/getTopBrandingUsers.ts b/src/routes/getTopBrandingUsers.ts index 370a63f..00b9560 100644 --- a/src/routes/getTopBrandingUsers.ts +++ b/src/routes/getTopBrandingUsers.ts @@ -1,5 +1,6 @@ import { db } from "../databases/databases"; import { Request, Response } from "express"; +import { Logger } from "../utils/logger"; async function generateTopUsersStats(sortBy: string) { const rows = await db.prepare("all", `SELECT COUNT(distinct "titles"."UUID") as "titleCount", COUNT(distinct "thumbnails"."UUID") as "thumbnailCount", COALESCE("userName", "titles"."userID") as "userName" @@ -36,8 +37,13 @@ export async function getTopBrandingUsers(req: Request, res: Response): Promise< return res.status(503).send("Disabled for load reasons"); } - const stats = await generateTopUsersStats(sortBy); + try { + const stats = await generateTopUsersStats(sortBy); - //send this result - return res.send(stats); + //send this result + return res.send(stats); + } catch (e) { + Logger.error(e as string); + return res.sendStatus(500); + } } diff --git a/src/routes/getTopCategoryUsers.ts b/src/routes/getTopCategoryUsers.ts index c8ce0d0..51c51a7 100644 --- a/src/routes/getTopCategoryUsers.ts +++ b/src/routes/getTopCategoryUsers.ts @@ -3,6 +3,7 @@ import { createMemoryCache } from "../utils/createMemoryCache"; import { config } from "../config"; import { Request, Response } from "express"; import { validateCategories } from "../utils/parseParams"; +import { Logger } from "../utils/logger"; const MILLISECONDS_IN_MINUTE = 60000; // eslint-disable-next-line @typescript-eslint/no-misused-promises @@ -74,8 +75,13 @@ export async function getTopCategoryUsers(req: Request, res: Response): Promise< return res.sendStatus(400); } - const stats = await getTopCategoryUsersWithCache(sortBy, category); + try { + const stats = await getTopCategoryUsersWithCache(sortBy, category); - //send this result - return res.send(stats); + //send this result + return res.send(stats); + } catch (e) { + Logger.error(e as string); + return res.sendStatus(500); + } } diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index f77bc8b..328da4e 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -2,6 +2,7 @@ import { db } from "../databases/databases"; import { createMemoryCache } from "../utils/createMemoryCache"; import { config } from "../config"; import { Request, Response } from "express"; +import { Logger } from "../utils/logger"; const MILLISECONDS_IN_MINUTE = 60000; // eslint-disable-next-line @typescript-eslint/no-misused-promises @@ -92,8 +93,13 @@ export async function getTopUsers(req: Request, res: Response): Promise { - const countContributingUsers = Boolean(req.query?.countContributingUsers == "true"); - const row = await getStats(countContributingUsers); - lastFetch = row; + try { + const countContributingUsers = Boolean(req.query?.countContributingUsers == "true"); + const row = await getStats(countContributingUsers); + lastFetch = row; - /* istanbul ignore if */ - if (!row) res.sendStatus(500); - const extensionUsers = chromeUsersCache + firefoxUsersCache; + /* istanbul ignore if */ + if (!row) res.sendStatus(500); + const extensionUsers = chromeUsersCache + firefoxUsersCache; - //send this result - res.send({ - userCount: row.userCount ?? 0, - activeUsers: extensionUsers, - apiUsers: Math.max(apiUsersCache, extensionUsers), - viewCount: row.viewCount, - totalSubmissions: row.totalSubmissions, - minutesSaved: row.minutesSaved, - }); + //send this result + res.send({ + userCount: row.userCount ?? 0, + activeUsers: extensionUsers, + apiUsers: Math.max(apiUsersCache, extensionUsers), + viewCount: row.viewCount, + totalSubmissions: row.totalSubmissions, + minutesSaved: row.minutesSaved, + }); - // Check if the cache should be updated (every ~14 hours) - const now = Date.now(); - if (now - lastUserCountCheck > 5000000) { - lastUserCountCheck = now; + // Check if the cache should be updated (every ~14 hours) + const now = Date.now(); + if (now - lastUserCountCheck > 5000000) { + lastUserCountCheck = now; - updateExtensionUsers(); + updateExtensionUsers(); + } + } catch (e) { + Logger.error(e as string); + res.sendStatus(500); } } diff --git a/src/routes/getUserID.ts b/src/routes/getUserID.ts index 5acf659..83d3702 100644 --- a/src/routes/getUserID.ts +++ b/src/routes/getUserID.ts @@ -1,6 +1,7 @@ import { db } from "../databases/databases"; import { Request, Response } from "express"; import { UserID } from "../types/user.model"; +import { Logger } from "../utils/logger"; function getFuzzyUserID(userName: string): Promise<{userName: string, userID: UserID }[]> { // escape [_ % \] to avoid ReDOS @@ -37,16 +38,22 @@ export async function getUserID(req: Request, res: Response): Promise // invalid request return res.sendStatus(400); } - const results = exactSearch - ? await getExactUserID(userName) - : await getFuzzyUserID(userName); - if (results === undefined || results === null) { - /* istanbul ignore next */ + try { + const results = exactSearch + ? await getExactUserID(userName) + : await getFuzzyUserID(userName); + + if (results === undefined || results === null) { + /* istanbul ignore next */ + return res.sendStatus(500); + } else if (results.length === 0) { + return res.sendStatus(404); + } else { + return res.send(results); + } + } catch (e) { + Logger.error(e as string); return res.sendStatus(500); - } else if (results.length === 0) { - return res.sendStatus(404); - } else { - return res.send(results); } } diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index dbcdfd3..fce3e33 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -205,19 +205,24 @@ async function getUserInfo(req: Request, res: Response): Promise { return res.status(400).send("Invalid userID or publicUserID parameter"); } - const responseObj = {} as Record; - for (const property of paramValues) { - responseObj[property] = await dbGetValue(hashedUserID, property); - } + try { + const responseObj = {} as Record; + for (const property of paramValues) { + responseObj[property] = await dbGetValue(hashedUserID, property); + } - // add minutesSaved and segmentCount after to avoid getting overwritten - if (paramValues.includes("minutesSaved") || paramValues.includes("segmentCount")) { - const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID); - responseObj["minutesSaved"] = segmentsSummary.minutesSaved; - responseObj["segmentCount"] = segmentsSummary.segmentCount; - } + // add minutesSaved and segmentCount after to avoid getting overwritten + if (paramValues.includes("minutesSaved") || paramValues.includes("segmentCount")) { + const segmentsSummary = await dbGetSubmittedSegmentSummary(hashedUserID); + responseObj["minutesSaved"] = segmentsSummary.minutesSaved; + responseObj["segmentCount"] = segmentsSummary.segmentCount; + } - return res.send(responseObj); + return res.send(responseObj); + } catch (err) { + Logger.error(err as string); + return res.sendStatus(500); + } } export async function endpoint(req: Request, res: Response): Promise { diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index c6c6fce..a930fb5 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -515,38 +515,37 @@ export async function postSkipSegments(req: Request, res: Response): Promise Logger.error(`call send webhooks ${e}`)); + } + + return res.json(newSegments); } catch (err) { Logger.error(err as string); - lock.unlock(); return res.sendStatus(500); + } finally { + lock.unlock(); } - - for (let i = 0; i < segments.length; i++) { - sendWebhooks(apiVideoDetails, userID, videoID, UUIDs[i], segments[i], service).catch((e) => Logger.error(`call send webhooks ${e}`)); - } - - lock.unlock(); - return res.json(newSegments); } // Takes an array of arrays: diff --git a/src/routes/postWarning.ts b/src/routes/postWarning.ts index 3056aec..aed4094 100644 --- a/src/routes/postWarning.ts +++ b/src/routes/postWarning.ts @@ -42,53 +42,58 @@ export async function postWarning(req: Request, res: Response): Promise 0) { return res.sendStatus(200); diff --git a/src/routes/shadowBanUser.ts b/src/routes/shadowBanUser.ts index 9920180..b0b5e8a 100644 --- a/src/routes/shadowBanUser.ts +++ b/src/routes/shadowBanUser.ts @@ -7,6 +7,7 @@ import { UserID } from "../types/user.model"; import { QueryCacher } from "../utils/queryCacher"; import { isUserVIP } from "../utils/isUserVIP"; import { parseCategories, parseDeArrowTypes } from "../utils/parseParams"; +import { Logger } from "../utils/logger"; export async function shadowBanUser(req: Request, res: Response): Promise { const userID = req.query.userID as UserID; @@ -36,42 +37,47 @@ export async function shadowBanUser(req: Request, res: Response): Promise ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[]; + const ips = (await Promise.all(timeSubmitted.map((s) => { + return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>; + }))).flat(); + + await Promise.all([...new Set(ips.map((ip) => ip.hashedIP))].map((ip) => { + return banIP(ip, enabled, unHideOldSubmissions, type, categories, deArrowTypes, true); + })); + } + + if (result) { + res.sendStatus(result); + return; + } + } else if (hashedIP) { + const result = await banIP(hashedIP, enabled, unHideOldSubmissions, type, categories, deArrowTypes, banUsers); + if (result) { + res.sendStatus(result); + return; + } + } + return res.sendStatus(200); + } catch (e) { + Logger.error(e as string); + return res.sendStatus(500); } - - if (userID) { - const result = await banUser(userID, enabled, unHideOldSubmissions, type, categories, deArrowTypes); - - if (enabled && lookForIPs) { - const ipLoggingFixedTime = 1675295716000; - const timeSubmitted = (await db.prepare("all", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" > ? AND "userID" = ?`, [ipLoggingFixedTime, userID])) as { timeSubmitted: number }[]; - const ips = (await Promise.all(timeSubmitted.map((s) => { - return privateDB.prepare("all", `SELECT "hashedIP" FROM "sponsorTimes" WHERE "timeSubmitted" = ?`, [s.timeSubmitted]) as Promise<{ hashedIP: HashedIP }[]>; - }))).flat(); - - await Promise.all([...new Set(ips.map((ip) => ip.hashedIP))].map((ip) => { - return banIP(ip, enabled, unHideOldSubmissions, type, categories, deArrowTypes, true); - })); - } - - if (result) { - res.sendStatus(result); - return; - } - } else if (hashedIP) { - const result = await banIP(hashedIP, enabled, unHideOldSubmissions, type, categories, deArrowTypes, banUsers); - if (result) { - res.sendStatus(result); - return; - } - } - return res.sendStatus(200); } export async function banUser(userID: UserID, enabled: boolean, unHideOldSubmissions: boolean, diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts index a06acf0..369a1a7 100644 --- a/src/routes/verifyToken.ts +++ b/src/routes/verifyToken.ts @@ -17,50 +17,55 @@ export const validateLicenseKeyRegex = (token: string) => export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise { const { query: { licenseKey } } = req; - if (!licenseKey) { - return res.status(400).send("Invalid request"); - } else if (!validateLicenseKeyRegex(licenseKey)) { - // fast check for invalid licence key - return res.status(200).send({ - allowed: false - }); - } - - const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?` - , [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number}; - if (tokens) { - const identity = await getPatreonIdentity(tokens.accessToken); - - if (tokens.expiresIn < 15 * 24 * 60 * 60) { - refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch((e) => Logger.error(`refresh token: ${e}`)); + try { + if (!licenseKey) { + return res.status(400).send("Invalid request"); + } else if (!validateLicenseKeyRegex(licenseKey)) { + // fast check for invalid licence key + return res.status(200).send({ + allowed: false + }); } - /* istanbul ignore else */ - if (identity) { - const membership = identity.included?.[0]?.attributes; - const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0) - || (membership.patron_status === PatronStatus.former && membership.campaign_lifetime_support_cents > 300)); + const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?` + , [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number}; + if (tokens) { + const identity = await getPatreonIdentity(tokens.accessToken); - return res.status(200).send({ - allowed - }); + if (tokens.expiresIn < 15 * 24 * 60 * 60) { + refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken).catch((e) => Logger.error(`refresh token: ${e}`)); + } + + /* istanbul ignore else */ + if (identity) { + const membership = identity.included?.[0]?.attributes; + const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0) + || (membership.patron_status === PatronStatus.former && membership.campaign_lifetime_support_cents > 300)); + + return res.status(200).send({ + allowed + }); + } else { + return res.status(500); + } } else { - return res.status(500); - } - } else { - // Check Local - const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]); - if (result) { - return res.status(200).send({ - allowed: true - }); - } else { - // Gumroad - return res.status(200).send({ - allowed: await checkAllGumroadProducts(licenseKey) - }); - } + // Check Local + const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]); + if (result) { + return res.status(200).send({ + allowed: true + }); + } else { + // Gumroad + return res.status(200).send({ + allowed: await checkAllGumroadProducts(licenseKey) + }); + } + } + } catch (e) { + Logger.error(e as string); + return res.status(500); } }