clear rating cache

This commit is contained in:
Michael C
2021-11-15 01:50:09 -05:00
parent 03c95ca158
commit ad7574308f
5 changed files with 130 additions and 17 deletions

View File

@@ -45,6 +45,7 @@ import { youtubeApiProxy } from "./routes/youtubeApiProxy";
import { getChapterNames } from "./routes/getChapterNames"; import { getChapterNames } from "./routes/getChapterNames";
import { postRating } from "./routes/ratings/postRating"; import { postRating } from "./routes/ratings/postRating";
import { getRating } from "./routes/ratings/getRating"; import { getRating } from "./routes/ratings/getRating";
import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache";
export function createServer(callback: () => void): Server { export function createServer(callback: () => void): Server {
// Create a service (the app object is just a callback). // Create a service (the app object is just a callback).
@@ -193,6 +194,7 @@ function setupRoutes(router: Router) {
// ratings // ratings
router.get("/api/ratings/rate/:prefix", getRating); router.get("/api/ratings/rate/:prefix", getRating);
router.post("/api/ratings/rate", postRateEndpoints); router.post("/api/ratings/rate", postRateEndpoints);
router.post("/api/ratings/clearCache", ratingPostClearCache);
if (config.postgres) { if (config.postgres) {
router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database", (req, res) => dumpDatabase(req, res, true));

View File

@@ -0,0 +1,52 @@
import { Logger } from "../../utils/logger";
import { HashedUserID, UserID } from "../../types/user.model";
import { getHash } from "../../utils/getHash";
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<Response> {
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 = getHash(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);
}
}

View File

@@ -8,6 +8,7 @@ import { getIP } from "../../utils/getIP";
import { getService } from "../../utils/getService"; import { getService } from "../../utils/getService";
import { RatingType, RatingTypes } from "../../types/ratings.model"; import { RatingType, RatingTypes } from "../../types/ratings.model";
import { config } from "../../config"; import { config } from "../../config";
import { QueryCacher } from "../../utils/queryCacher";
export async function postRating(req: Request, res: Response): Promise<Response> { export async function postRating(req: Request, res: Response): Promise<Response> {
const privateUserID = req.body.userID as UserID; const privateUserID = req.body.userID as UserID;
@@ -34,13 +35,13 @@ export async function postRating(req: Request, res: Response): Promise<Response>
// Undo the vote // Undo the vote
await db.prepare("run", `UPDATE "ratings" SET "count" = "count" - 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); await db.prepare("run", `UPDATE "ratings" SET "count" = "count" - 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]);
await privateDB.prepare("run", `DELETE FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]); await privateDB.prepare("run", `DELETE FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]);
return res.sendStatus(200);
} else if (existingVote.count === 0 && enabled) { } else if (existingVote.count === 0 && enabled) {
// Make sure there hasn't been another vote from this IP // 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])) 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; .count > 0;
if (!existingIPVote) { if (existingIPVote) { // if exisiting vote, exit early instead
return res.sendStatus(200);
}
// Check if general rating already exists, if so increase it // 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]); 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) { if (rating.count > 0) {
@@ -52,8 +53,8 @@ export async function postRating(req: Request, res: Response): Promise<Response>
// Create entry in privateDB // 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]); await privateDB.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "userID", "timeSubmitted", "hashedIP") VALUES (?, ?, ?, ?, ?, ?)`, [videoID, service, type, hashedUserID, Date.now(), hashedIP]);
} }
} // clear rating cache
QueryCacher.clearRatingCache({ hashedVideoID, service });
return res.sendStatus(200); return res.sendStatus(200);
} catch (err) { } catch (err) {
Logger.error(err as string); Logger.error(err as string);

View File

@@ -1,6 +1,6 @@
import redis from "../utils/redis"; import redis from "../utils/redis";
import { Logger } from "../utils/logger"; import { Logger } from "../utils/logger";
import { skipSegmentsHashKey, skipSegmentsKey, reputationKey } from "./redisKeys"; import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey } from "./redisKeys";
import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Service, VideoID, VideoIDHash } from "../types/segments.model";
import { UserID } from "../types/user.model"; import { UserID } from "../types/user.model";
@@ -22,7 +22,7 @@ async function get<T>(fetchFromDB: () => Promise<T>, key: string): Promise<T> {
return data; return data;
} }
function clearVideoCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; userID?: UserID; }): void { function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; userID?: UserID; }): void {
if (videoInfo) { if (videoInfo) {
redis.delAsync(skipSegmentsKey(videoInfo.videoID, videoInfo.service)); redis.delAsync(skipSegmentsKey(videoInfo.videoID, videoInfo.service));
redis.delAsync(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)); redis.delAsync(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service));
@@ -30,7 +30,14 @@ function clearVideoCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHa
} }
} }
function clearRatingCache(videoInfo: { hashedVideoID: VideoIDHash; service: Service;}): void {
if (videoInfo) {
redis.delAsync(ratingHashKey(videoInfo.hashedVideoID, videoInfo.service));
}
}
export const QueryCacher = { export const QueryCacher = {
get, get,
clearVideoCache clearSegmentCache,
clearRatingCache
}; };

View File

@@ -0,0 +1,51 @@
import { db } from "../../../src/databases/databases";
import { getHash } from "../../../src/utils/getHash";
import assert from "assert";
import { client } from "../../utils/httpClient";
const VIPUser = "clearCacheVIP";
const regularUser = "regular-user";
const endpoint = "/api/ratings/clearCache";
const postClearCache = (userID: string, videoID: string) => client({ method: "post", url: endpoint, params: { userID, videoID } });
describe("ratings postClearCache", () => {
before(async () => {
await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES ('${getHash(VIPUser)}')`);
});
it("Should be able to clear cache amy video", (done) => {
postClearCache(VIPUser, "dne-video")
.then(res => {
assert.strictEqual(res.status, 200);
done();
})
.catch(err => done(err));
});
it("Should get 403 as non-vip", (done) => {
postClearCache(regularUser, "clear-test")
.then(res => {
assert.strictEqual(res.status, 403);
done();
})
.catch(err => done(err));
});
it("Should give 400 with missing videoID", (done) => {
client.post(endpoint, { params: { userID: VIPUser } })
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
it("Should give 400 with missing userID", (done) => {
client.post(endpoint, { params: { videoID: "clear-test" } })
.then(res => {
assert.strictEqual(res.status, 400);
done();
})
.catch(err => done(err));
});
});