diff --git a/src/app.ts b/src/app.ts index 05c0e1f..92a94bf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,9 +43,6 @@ import ExpressPromiseRouter from "express-promise-router"; import { Server } from "http"; import { youtubeApiProxy } from "./routes/youtubeApiProxy"; import { getChapterNames } from "./routes/getChapterNames"; -import { postRating } from "./routes/ratings/postRating"; -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 { addFeature } from "./routes/addFeature"; @@ -80,11 +77,9 @@ function setupRoutes(router: Router) { // Rate limit endpoint lists const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; - const postRateEndpoints: RequestHandler[] = [postRating]; if (config.rateLimit) { if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote, voteGetUserID)); if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view)); - if (config.rateLimit.rate) postRateEndpoints.unshift(rateLimitMiddleware(config.rateLimit.rate)); } //add the get function @@ -199,12 +194,6 @@ function setupRoutes(router: Router) { router.post("/api/feature", addFeature); - // ratings - router.get("/api/ratings/rate/:prefix", getRating); - router.get("/api/ratings/rate", getRating); - router.post("/api/ratings/rate", postRateEndpoints); - router.post("/api/ratings/clearCache", ratingPostClearCache); - if (config.postgres?.enabled) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); diff --git a/src/config.ts b/src/config.ts index 936aa22..4c74aba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -62,12 +62,6 @@ addDefaults(config, { max: 10, statusCode: 200, message: "OK", - }, - rate: { - windowMs: 900000, - max: 20, - statusCode: 200, - message: "Success", } }, userCounterURL: null, diff --git a/src/routes/ratings/getRating.ts b/src/routes/ratings/getRating.ts deleted file mode 100644 index b3fd5c0..0000000 --- a/src/routes/ratings/getRating.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Request, Response } from "express"; -import { db } from "../../databases/databases"; -import { RatingType } from "../../types/ratings.model"; -import { Service, VideoID, VideoIDHash } from "../../types/segments.model"; -import { getService } from "../../utils/getService"; -import { hashPrefixTester } from "../../utils/hashPrefixTester"; -import { Logger } from "../../utils/logger"; -import { QueryCacher } from "../../utils/queryCacher"; -import { ratingHashKey } from "../../utils/redisKeys"; - -interface DBRating { - videoID: VideoID, - hashedVideoID: VideoIDHash, - service: Service, - type: RatingType, - count: number -} - -export async function getRating(req: Request, res: Response): Promise { - let hashPrefixes: VideoIDHash[] = []; - try { - hashPrefixes = req.query.hashPrefixes - ? JSON.parse(req.query.hashPrefixes as string) - : Array.isArray(req.query.prefix) - ? req.query.prefix - : [req.query.prefix ?? req.params.prefix]; - if (!Array.isArray(hashPrefixes)) { - return res.status(400).send("hashPrefixes parameter does not match format requirements."); - } - - hashPrefixes.map((hashPrefix) => hashPrefix?.toLowerCase()); - } catch(error) { - return res.status(400).send("Bad parameter: hashPrefixes (invalid JSON)"); - } - if (hashPrefixes.length === 0 || hashPrefixes.length > 75 - || hashPrefixes.some((hashPrefix) => !hashPrefix || !hashPrefixTester(hashPrefix))) { - return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix - } - - let types: RatingType[] = []; - try { - types = req.query.types - ? JSON.parse(req.query.types as string) - : req.query.type - ? Array.isArray(req.query.type) - ? req.query.type - : [req.query.type] - : [RatingType.Upvote, RatingType.Downvote]; - if (!Array.isArray(types)) { - return res.status(400).send("Types parameter does not match format requirements."); - } - - types = types.map((type) => parseInt(type as unknown as string, 10)); - } catch(error) { - return res.status(400).send("Bad parameter: types (invalid JSON)"); - } - - const service: Service = getService(req.query.service, req.body.service); - - try { - const ratings = (await getRatings(hashPrefixes, service)) - .filter((rating) => types.includes(rating.type)) - .map((rating) => ({ - videoID: rating.videoID, - hash: rating.hashedVideoID, - service: rating.service, - type: rating.type, - count: rating.count - })); - - return res.status((ratings.length) ? 200 : 404) - .send(ratings ?? []); - } catch (err) { - Logger.error(err as string); - - return res.sendStatus(500); - } -} - -function getRatings(hashPrefixes: VideoIDHash[], service: Service): Promise { - const fetchFromDB = (hashPrefixes: VideoIDHash[]) => db - .prepare( - "all", - `SELECT "videoID", "hashedVideoID", "type", "count" FROM "ratings" WHERE "hashedVideoID" ~* ? AND "service" = ? ORDER BY "hashedVideoID"`, - [`^(?:${hashPrefixes.join("|")})`, service] - ) as Promise; - - return (hashPrefixes.every((hashPrefix) => hashPrefix.length === 4)) - ? QueryCacher.getAndSplit(fetchFromDB, (prefix) => ratingHashKey(prefix, service), "hashedVideoID", hashPrefixes) - : fetchFromDB(hashPrefixes); -} \ No newline at end of file diff --git a/src/routes/ratings/postClearCache.ts b/src/routes/ratings/postClearCache.ts deleted file mode 100644 index 38a8707..0000000 --- a/src/routes/ratings/postClearCache.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Logger } from "../../utils/logger"; -import { HashedUserID, UserID } from "../../types/user.model"; -import { getHash } from "../../utils/getHash"; -import { getHashCache } from "../../utils/getHashCache"; -import { Request, Response } from "express"; -import { Service, VideoID } from "../../types/segments.model"; -import { QueryCacher } from "../../utils/queryCacher"; -import { isUserVIP } from "../../utils/isUserVIP"; -import { VideoIDHash } from "../../types/segments.model"; -import { getService } from "../..//utils/getService"; - -export async function postClearCache(req: Request, res: Response): Promise { - const videoID = req.query.videoID as VideoID; - const userID = req.query.userID as UserID; - const service = getService(req.query.service as Service); - - const invalidFields = []; - if (typeof videoID !== "string") { - invalidFields.push("videoID"); - } - if (typeof userID !== "string") { - invalidFields.push("userID"); - } - - if (invalidFields.length !== 0) { - // invalid request - const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ", " : "") + c, ""); - return res.status(400).send(`No valid ${fields} field(s) provided`); - } - - // hash the userID as early as possible - const hashedUserID: HashedUserID = await getHashCache(userID); - // hash videoID - const hashedVideoID: VideoIDHash = getHash(videoID, 1); - - // Ensure user is a VIP - if (!(await isUserVIP(hashedUserID))){ - Logger.warn(`Permission violation: User ${hashedUserID} attempted to clear cache for video ${videoID}.`); - return res.status(403).json({ "message": "Not a VIP" }); - } - - try { - QueryCacher.clearRatingCache({ - hashedVideoID, - service - }); - return res.status(200).json({ - message: `Cache cleared on video ${videoID}` - }); - } catch(err) { - return res.sendStatus(500); - } -} diff --git a/src/routes/ratings/postRating.ts b/src/routes/ratings/postRating.ts deleted file mode 100644 index 866fe57..0000000 --- a/src/routes/ratings/postRating.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { db, privateDB } from "../../databases/databases"; -import { getHash } from "../../utils/getHash"; -import { getHashCache } from "../../utils/getHashCache"; -import { Logger } from "../../utils/logger"; -import { Request, Response } from "express"; -import { HashedUserID, UserID } from "../../types/user.model"; -import { HashedIP, IPAddress, VideoID } from "../../types/segments.model"; -import { getIP } from "../../utils/getIP"; -import { getService } from "../../utils/getService"; -import { RatingType, RatingTypes } from "../../types/ratings.model"; -import { config } from "../../config"; -import { QueryCacher } from "../../utils/queryCacher"; - -export async function postRating(req: Request, res: Response): Promise { - const privateUserID = req.body.userID as UserID; - const videoID = req.body.videoID as VideoID; - const service = getService(req.query.service, req.body.service); - const type = req.body.type as RatingType; - const enabled = req.body.enabled ?? true; - - if (privateUserID == undefined || videoID == undefined || service == undefined || type == undefined - || (typeof privateUserID !== "string") || (typeof videoID !== "string") || (typeof service !== "string") - || (typeof type !== "number") || (enabled && (typeof enabled !== "boolean")) || !RatingTypes.includes(type)) { - //invalid request - return res.sendStatus(400); - } - - const hashedIP: HashedIP = getHash(getIP(req) + config.globalSalt as IPAddress, 1); - const hashedUserID: HashedUserID = await getHashCache(privateUserID); - const hashedVideoID = getHash(videoID, 1); - - try { - // Check if this user has voted before - const existingVote = await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]); - if (existingVote.count > 0 && !enabled) { - // Undo the vote - await privateDB.prepare("run", `DELETE FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]); - await db.prepare("run", `UPDATE "ratings" SET "count" = "count" - 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - } else if (existingVote.count === 0 && enabled) { - // Make sure there hasn't been another vote from this IP - const existingIPVote = (await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "hashedIP" = ?`, [videoID, service, type, hashedIP])) - .count > 0; - if (existingIPVote) { // if exisiting vote, exit early instead - return res.sendStatus(200); - } - - // Create entry in privateDB - await privateDB.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "userID", "timeSubmitted", "hashedIP") VALUES (?, ?, ?, ?, ?, ?)`, [videoID, service, type, hashedUserID, Date.now(), hashedIP]); - - // Check if general rating already exists, if so increase it - const rating = await db.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - if (rating.count > 0) { - await db.prepare("run", `UPDATE "ratings" SET "count" = "count" + 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - } else { - await db.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "count", "hashedVideoID") VALUES (?, ?, ?, 1, ?)`, [videoID, service, type, hashedVideoID]); - } - } - // clear rating cache - QueryCacher.clearRatingCache({ hashedVideoID, service }); - return res.sendStatus(200); - } catch (err) { - Logger.error(err as string); - return res.sendStatus(500); - } -} \ No newline at end of file diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 53b39cd..56a271c 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -52,7 +52,6 @@ export interface SBSConfig { rateLimit: { vote: RateLimitConfig; view: RateLimitConfig; - rate: RateLimitConfig; }; mysql?: any; privateMysql?: any;