Merge branch 'master' into categoryLeaderboards

This commit is contained in:
Ajay Ramachandran
2021-12-31 14:04:20 -05:00
committed by GitHub
16 changed files with 353 additions and 25 deletions

View File

@@ -207,6 +207,7 @@
[categoryVotes](#categoryVotes) [categoryVotes](#categoryVotes)
[sponsorTimes](#sponsorTimes) [sponsorTimes](#sponsorTimes)
[config](#config) [config](#config)
[tempVipLog](#tempVipLog)
### vote ### vote
@@ -270,3 +271,11 @@
| index | field | | index | field |
| -- | :--: | | -- | :--: |
| ratings_videoID | videoID, service, userID, timeSubmitted | | ratings_videoID | videoID, service, userID, timeSubmitted |
### tempVipLog
| Name | Type | |
| -- | :--: | -- |
| issuerUserID | TEXT | not null |
| targetUserID | TEXT | not null |
| enabled | BOOLEAN | not null |
| updatedAt | INTEGER | not null |

View File

@@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS "ratings" (
"service" TEXT NOT NULL default 'YouTube', "service" TEXT NOT NULL default 'YouTube',
"type" INTEGER NOT NULL, "type" INTEGER NOT NULL,
"userID" TEXT NOT NULL, "userID" TEXT NOT NULL,
"timeSubmitted" INTEGER NOT NULL, "timeSubmitted" INTEGER NOT NULL,
"hashedIP" TEXT NOT NULL "hashedIP" TEXT NOT NULL
); );

View File

@@ -0,0 +1,12 @@
BEGIN TRANSACTION;
CREATE TABLE IF NOT EXISTS "tempVipLog" (
"issuerUserID" TEXT NOT NULL,
"targetUserID" TEXT NOT NULL,
"enabled" BOOLEAN NOT NULL,
"updatedAt" INTEGER NOT NULL
);
UPDATE "config" SET value = 5 WHERE key = 'version';
COMMIT;

View File

@@ -26,6 +26,8 @@ http {
keepalive_timeout 5; keepalive_timeout 5;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection ""; proxy_set_header Connection "";
access_log off;
error_log /dev/null crit;
upstream backend_GET { upstream backend_GET {
ip_hash; ip_hash;

View File

@@ -45,8 +45,9 @@ 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"; import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache"
import { getTopCategoryUsers } from "./routes/getTopCategoryUsers"; import { getTopCategoryUsers } from "./routes/getTopCategoryUsers";
import { addUserAsTempVIP } from "./routes/addUserAsTempVIP";
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).
@@ -118,6 +119,8 @@ function setupRoutes(router: Router) {
//Endpoint used to make a user a VIP user with special privileges //Endpoint used to make a user a VIP user with special privileges
router.post("/api/addUserAsVIP", addUserAsVIP); router.post("/api/addUserAsVIP", addUserAsVIP);
//Endpoint to add a user as a temporary VIP
router.post("/api/addUserAsTempVIP", addUserAsTempVIP);
//Gets all the views added up for one userID //Gets all the views added up for one userID
//Useful to see how much one user has contributed //Useful to see how much one user has contributed

View File

@@ -0,0 +1,71 @@
import { VideoID } from "../types/segments.model";
import { YouTubeAPI } from "../utils/youtubeApi";
import { APIVideoInfo } from "../types/youtubeApi.model";
import { config } from "../config";
import { getHashCache } from "../utils/getHashCache";
import { privateDB } from "../databases/databases";
import { Request, Response } from "express";
import { isUserVIP } from "../utils/isUserVIP";
import { HashedUserID } from "../types/user.model";
import redis from "../utils/redis";
import { tempVIPKey } from "../utils/redisKeys";
interface AddUserAsTempVIPRequest extends Request {
query: {
userID: HashedUserID;
adminUserID: string;
enabled: string;
channelVideoID: string;
}
}
function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<APIVideoInfo> {
return (config.newLeafURLs) ? YouTubeAPI.listVideos(videoID, ignoreCache) : null;
}
const getChannelInfo = async (videoID: VideoID): Promise<{id: string | null, name: string | null }> => {
const videoInfo = await getYouTubeVideoInfo(videoID);
return {
id: videoInfo?.data?.authorId,
name: videoInfo?.data?.author
};
};
export async function addUserAsTempVIP(req: AddUserAsTempVIPRequest, res: Response): Promise<Response> {
const { query: { userID, adminUserID } } = req;
const enabled = req.query?.enabled === "true";
const channelVideoID = req.query?.channelVideoID as VideoID;
if (!userID || !adminUserID || !channelVideoID ) {
// invalid request
return res.sendStatus(400);
}
// hash the issuer userID
const issuerUserID = await getHashCache(adminUserID);
// check if issuer is VIP
const issuerIsVIP = await isUserVIP(issuerUserID as HashedUserID);
if (!issuerIsVIP) {
return res.sendStatus(403);
}
// check to see if this user is already a vip
const targetIsVIP = await isUserVIP(userID);
if (targetIsVIP) {
return res.sendStatus(409);
}
const startTime = Date.now();
const dayInSeconds = 86400;
const channelInfo = await getChannelInfo(channelVideoID);
await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]);
if (enabled) { // add to redis
await redis.setAsyncEx(tempVIPKey(userID), channelInfo?.id, dayInSeconds);
} else { // delete key
await redis.delAsync(tempVIPKey(userID));
}
return res.sendStatus(200);
}

View File

@@ -307,7 +307,7 @@ function splitPercentOverlap(groups: OverlappingSegmentGroup[]): OverlappingSegm
const overlapPercent = overlap / overallDuration; const overlapPercent = overlap / overallDuration;
return (overlapPercent > 0 && segment.actionType === compareSegment.actionType && segment.category === compareSegment.category && segment.actionType !== ActionType.Chapter) return (overlapPercent > 0 && segment.actionType === compareSegment.actionType && segment.category === compareSegment.category && segment.actionType !== ActionType.Chapter)
|| (overlapPercent >= 0.6 && segment.actionType !== compareSegment.actionType && segment.category === compareSegment.category) || (overlapPercent >= 0.6 && segment.actionType !== compareSegment.actionType && segment.category === compareSegment.category)
|| (overlapPercent >= 0.8 && segment.actionType === compareSegment.actionType && segment.category !== compareSegment.category || (overlapPercent >= 0.95 && segment.actionType === compareSegment.actionType && segment.category !== compareSegment.category
&& segment.category !== "music_offtopic" && compareSegment.category !== "music_offtopic") && segment.category !== "music_offtopic" && compareSegment.category !== "music_offtopic")
|| (overlapPercent >= 0.8 && segment.actionType === ActionType.Chapter && compareSegment.actionType === ActionType.Chapter); || (overlapPercent >= 0.8 && segment.actionType === ActionType.Chapter && compareSegment.actionType === ActionType.Chapter);
}); });

View File

@@ -2,6 +2,7 @@ import { db } from "../databases/databases";
import { Logger } from "../utils/logger"; import { Logger } from "../utils/logger";
import { Request, Response } from "express"; import { Request, Response } from "express";
import os from "os"; import os from "os";
import redis from "../utils/redis";
export async function getStatus(req: Request, res: Response): Promise<Response> { export async function getStatus(req: Request, res: Response): Promise<Response> {
const startTime = Date.now(); const startTime = Date.now();
@@ -9,13 +10,16 @@ export async function getStatus(req: Request, res: Response): Promise<Response>
value = Array.isArray(value) ? value[0] : value; value = Array.isArray(value) ? value[0] : value;
try { try {
const dbVersion = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value; const dbVersion = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value;
const numberRequests = await redis.increment("statusRequest");
const statusRequests = numberRequests?.replies?.[0];
const statusValues: Record<string, any> = { const statusValues: Record<string, any> = {
uptime: process.uptime(), uptime: process.uptime(),
commit: (global as any).HEADCOMMIT || "unknown", commit: (global as any).HEADCOMMIT || "unknown",
db: Number(dbVersion), db: Number(dbVersion),
startTime, startTime,
processTime: Date.now() - startTime, processTime: Date.now() - startTime,
loadavg: os.loadavg().slice(1) // only return 5 & 15 minute load average loadavg: os.loadavg().slice(1), // only return 5 & 15 minute load average
statusRequests
}; };
return value ? res.send(JSON.stringify(statusValues[value])) : res.send(statusValues); return value ? res.send(JSON.stringify(statusValues[value])) : res.send(statusValues);
} catch (err) { } catch (err) {

View File

@@ -9,11 +9,13 @@ import { getFormattedTime } from "../utils/getFormattedTime";
import { getIP } from "../utils/getIP"; import { getIP } from "../utils/getIP";
import { getHashCache } from "../utils/getHashCache"; import { getHashCache } from "../utils/getHashCache";
import { config } from "../config"; import { config } from "../config";
import { UserID } from "../types/user.model"; import { HashedUserID, UserID } from "../types/user.model";
import { Category, CategoryActionType, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, Visibility, VideoDuration } from "../types/segments.model"; import { Category, CategoryActionType, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, Visibility, VideoDuration } from "../types/segments.model";
import { getCategoryActionType } from "../utils/categoryInfo"; import { getCategoryActionType } from "../utils/categoryInfo";
import { QueryCacher } from "../utils/queryCacher"; import { QueryCacher } from "../utils/queryCacher";
import axios from "axios"; import axios from "axios";
import redis from "../utils/redis";
import { tempVIPKey } from "../utils/redisKeys";
const voteTypes = { const voteTypes = {
normal: 0, normal: 0,
@@ -37,6 +39,7 @@ interface VoteData {
UUID: string; UUID: string;
nonAnonUserID: string; nonAnonUserID: string;
voteTypeEnum: number; voteTypeEnum: number;
isTempVIP: boolean;
isVIP: boolean; isVIP: boolean;
isOwnSubmission: boolean; isOwnSubmission: boolean;
row: { row: {
@@ -57,6 +60,13 @@ function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise<API
} }
} }
const isUserTempVIP = async (nonAnonUserID: HashedUserID, videoID: VideoID): Promise<boolean> => {
const apiVideoInfo = await getYouTubeVideoInfo(videoID);
const channelID = apiVideoInfo?.data?.authorId;
const { err, reply } = await redis.getAsync(tempVIPKey(nonAnonUserID));
return err ? false : (reply == channelID);
};
const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2); const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2);
async function checkVideoDurationChange(UUID: SegmentUUID) { async function checkVideoDurationChange(UUID: SegmentUUID) {
@@ -105,7 +115,7 @@ async function sendWebhooks(voteData: VoteData) {
// Send custom webhooks // Send custom webhooks
dispatchEvent(isUpvote ? "vote.up" : "vote.down", { dispatchEvent(isUpvote ? "vote.up" : "vote.down", {
"user": { "user": {
"status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission), "status": getVoteAuthorRaw(userSubmissionCountRow.submissionCount, voteData.isTempVIP, voteData.isVIP, voteData.isOwnSubmission),
}, },
"video": { "video": {
"id": submissionInfoRow.videoID, "id": submissionInfoRow.videoID,
@@ -153,7 +163,7 @@ async function sendWebhooks(voteData: VoteData) {
"author": { "author": {
"name": voteData.finalResponse?.webhookMessage ?? "name": voteData.finalResponse?.webhookMessage ??
voteData.finalResponse?.finalMessage ?? voteData.finalResponse?.finalMessage ??
getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isVIP, voteData.isOwnSubmission), getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isTempVIP, voteData.isVIP, voteData.isOwnSubmission),
}, },
"thumbnail": { "thumbnail": {
"url": getMaxResThumbnail(data) || "", "url": getMaxResThumbnail(data) || "",
@@ -311,7 +321,9 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress); const hashedIP: HashedIP = await getHashCache((ip + config.globalSalt) as IPAddress);
//check if this user is on the vip list //check if this user is on the vip list
const isVIP = await isUserVIP(nonAnonUserID); const videoID = await db.prepare("get", `select "videoID" from "sponsorTimes" where "UUID" = ?`, [UUID]);
const isTempVIP = await isUserTempVIP(nonAnonUserID, videoID?.videoID || null);
const isVIP = await isUserVIP(nonAnonUserID) || isTempVIP;
//check if user voting on own submission //check if user voting on own submission
const isOwnSubmission = (await db.prepare("get", `SELECT "UUID" as "submissionCount" FROM "sponsorTimes" where "userID" = ? AND "UUID" = ?`, [nonAnonUserID, UUID])) !== undefined; const isOwnSubmission = (await db.prepare("get", `SELECT "UUID" as "submissionCount" FROM "sponsorTimes" where "userID" = ? AND "UUID" = ?`, [nonAnonUserID, UUID])) !== undefined;
@@ -480,6 +492,7 @@ export async function voteOnSponsorTime(req: Request, res: Response): Promise<Re
UUID, UUID,
nonAnonUserID, nonAnonUserID,
voteTypeEnum, voteTypeEnum,
isTempVIP,
isVIP, isVIP,
isOwnSubmission, isOwnSubmission,
row: videoInfo, row: videoInfo,

View File

@@ -7,8 +7,10 @@ interface RedisSB {
getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>; getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>;
set(key: string, value: string, callback?: Callback<string | null>): void; set(key: string, value: string, callback?: Callback<string | null>): void;
setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>; setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>;
setAsyncEx?(key: string, value: string, seconds: number): Promise<{err: Error | null, reply: string | null}>;
delAsync?(...keys: [string]): Promise<Error | null>; delAsync?(...keys: [string]): Promise<Error | null>;
close?(flush?: boolean): void; close?(flush?: boolean): void;
increment?(key: string): Promise<{err: Error| null, replies: any[] | null}>;
} }
let exportObject: RedisSB = { let exportObject: RedisSB = {
@@ -18,8 +20,12 @@ let exportObject: RedisSB = {
set: (key, value, callback) => callback(null, undefined), set: (key, value, callback) => callback(null, undefined),
setAsync: () => setAsync: () =>
new Promise((resolve) => resolve({ err: null, reply: undefined })), new Promise((resolve) => resolve({ err: null, reply: undefined })),
setAsyncEx: () =>
new Promise((resolve) => resolve({ err: null, reply: undefined })),
delAsync: () => delAsync: () =>
new Promise((resolve) => resolve(null)), new Promise((resolve) => resolve(null)),
increment: () =>
new Promise((resolve) => resolve({ err: null, replies: undefined })),
}; };
if (config.redis) { if (config.redis) {
@@ -29,9 +35,15 @@ if (config.redis) {
exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({ err, reply }))); exportObject.getAsync = (key) => new Promise((resolve) => client.get(key, (err, reply) => resolve({ err, reply })));
exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply }))); exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply })));
exportObject.setAsyncEx = (key, value, seconds) => new Promise((resolve) => client.setex(key, seconds, value, (err, reply) => resolve({ err, reply })));
exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err))); exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err)));
exportObject.close = (flush) => client.end(flush); exportObject.close = (flush) => client.end(flush);
exportObject.increment = (key) => new Promise((resolve) =>
client.multi()
.incr(key)
.expire(key, 60)
.exec((err, replies) => resolve({ err, replies }))
);
client.on("error", function(error) { client.on("error", function(error) {
Logger.error(error); Logger.error(error);
}); });

View File

@@ -1,26 +1,23 @@
import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Service, VideoID, VideoIDHash } from "../types/segments.model";
import { UserID } from "../types/user.model"; import { HashedUserID, UserID } from "../types/user.model";
import { HashedValue } from "../types/hash.model"; import { HashedValue } from "../types/hash.model";
import { Logger } from "./logger"; import { Logger } from "./logger";
export function skipSegmentsKey(videoID: VideoID, service: Service): string { export const skipSegmentsKey = (videoID: VideoID, service: Service): string =>
return `segments.v2.${service}.videoID.${videoID}`; `segments.v3.${service}.videoID.${videoID}`;
}
export function skipSegmentGroupsKey(videoID: VideoID, service: Service): string { export const skipSegmentGroupsKey = (videoID: VideoID, service: Service): string =>
return `segments.groups.${service}.videoID.${videoID}`; `segments.groups.v2.${service}.videoID.${videoID}`;
}
export function skipSegmentsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string { export function skipSegmentsHashKey(hashedVideoIDPrefix: VideoIDHash, service: Service): string {
hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 4) as VideoIDHash; hashedVideoIDPrefix = hashedVideoIDPrefix.substring(0, 4) as VideoIDHash;
if (hashedVideoIDPrefix.length !== 4) Logger.warn(`Redis skip segment hash-prefix key is not length 4! ${hashedVideoIDPrefix}`); if (hashedVideoIDPrefix.length !== 4) Logger.warn(`Redis skip segment hash-prefix key is not length 4! ${hashedVideoIDPrefix}`);
return `segments.v2.${service}.${hashedVideoIDPrefix}`; return `segments.v3.${service}.${hashedVideoIDPrefix}`;
} }
export function reputationKey(userID: UserID): string { export const reputationKey = (userID: UserID): string =>
return `reputation.user.${userID}`; `reputation.user.${userID}`;
}
export function ratingHashKey(hashPrefix: VideoIDHash, service: Service): string { export function ratingHashKey(hashPrefix: VideoIDHash, service: Service): string {
hashPrefix = hashPrefix.substring(0, 4) as VideoIDHash; hashPrefix = hashPrefix.substring(0, 4) as VideoIDHash;
@@ -34,3 +31,6 @@ export function shaHashKey(singleIter: HashedValue): string {
return `sha.hash.${singleIter}`; return `sha.hash.${singleIter}`;
} }
export const tempVIPKey = (userID: HashedUserID): string =>
`vip.temp.${userID}`;

View File

@@ -2,9 +2,11 @@ import { config } from "../config";
import { Logger } from "../utils/logger"; import { Logger } from "../utils/logger";
import axios from "axios"; import axios from "axios";
function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string { function getVoteAuthorRaw(submissionCount: number, isTempVIP: boolean, isVIP: boolean, isOwnSubmission: boolean): string {
if (isOwnSubmission) { if (isOwnSubmission) {
return "self"; return "self";
} else if (isTempVIP) {
return "temp vip";
} else if (isVIP) { } else if (isVIP) {
return "vip"; return "vip";
} else if (submissionCount === 0) { } else if (submissionCount === 0) {
@@ -14,11 +16,13 @@ function getVoteAuthorRaw(submissionCount: number, isVIP: boolean, isOwnSubmissi
} }
} }
function getVoteAuthor(submissionCount: number, isVIP: boolean, isOwnSubmission: boolean): string { function getVoteAuthor(submissionCount: number, isTempVIP: boolean, isVIP: boolean, isOwnSubmission: boolean): string {
if (submissionCount === 0) { if (submissionCount === 0) {
return "Report by New User"; return "Report by New User";
} else if (isOwnSubmission) { } else if (isOwnSubmission) {
return "Report by Submitter"; return "Report by Submitter";
} else if (isTempVIP) {
return "Report by Temp VIP";
} else if (isVIP) { } else if (isVIP) {
return "Report by VIP User"; return "Report by VIP User";
} }

View File

@@ -47,9 +47,9 @@ describe("getSkipSegmentsByHash", () => {
await db.prepare("run", query, ["requiredSegmentHashVid", 10, 20, -2, 0, "fbf0af454059733c8822f6a4ac8ec568e0787f8c0a5ee915dd5b05e0d7a9a388", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, requiredSegmentHashVidHash, ""]); await db.prepare("run", query, ["requiredSegmentHashVid", 10, 20, -2, 0, "fbf0af454059733c8822f6a4ac8ec568e0787f8c0a5ee915dd5b05e0d7a9a388", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, requiredSegmentHashVidHash, ""]);
await db.prepare("run", query, ["requiredSegmentHashVid", 20, 30, -2, 0, "7e1ebc5194551d2d0a606d64f675e5a14952e4576b2959f8c9d51e316c14f8da", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, requiredSegmentHashVidHash, ""]); await db.prepare("run", query, ["requiredSegmentHashVid", 20, 30, -2, 0, "7e1ebc5194551d2d0a606d64f675e5a14952e4576b2959f8c9d51e316c14f8da", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, requiredSegmentHashVidHash, ""]);
await db.prepare("run", query, ["differentCategoryVid", 60, 70, 2, 0, "differentCategoryVid-1", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, differentCategoryVidHash, ""]); await db.prepare("run", query, ["differentCategoryVid", 60, 70, 2, 0, "differentCategoryVid-1", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, differentCategoryVidHash, ""]);
await db.prepare("run", query, ["differentCategoryVid", 61, 70, 2, 1, "differentCategoryVid-2", "testman", 0, 50, "intro", "skip", "YouTube", 0, 0, differentCategoryVidHash, ""]); await db.prepare("run", query, ["differentCategoryVid", 60, 70, 2, 1, "differentCategoryVid-2", "testman", 0, 50, "intro", "skip", "YouTube", 0, 0, differentCategoryVidHash, ""]);
await db.prepare("run", query, ["nonMusicOverlapVid", 60, 70, 2, 0, "nonMusicOverlapVid-1", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]); await db.prepare("run", query, ["nonMusicOverlapVid", 60, 70, 2, 0, "nonMusicOverlapVid-1", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]);
await db.prepare("run", query, ["nonMusicOverlapVid", 61, 70, 2, 1, "nonMusicOverlapVid-2", "testman", 0, 50, "music_offtopic", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]); await db.prepare("run", query, ["nonMusicOverlapVid", 60, 70, 2, 1, "nonMusicOverlapVid-2", "testman", 0, 50, "music_offtopic", "skip", "YouTube", 0, 0, nonMusicOverlapVidHash, ""]);
}); });
it("Should be able to get a 200", (done) => { it("Should be able to get a 200", (done) => {

View File

@@ -1,6 +1,7 @@
import assert from "assert"; import assert from "assert";
import { db } from "../../src/databases/databases"; import { db } from "../../src/databases/databases";
import { client } from "../utils/httpClient"; import { client } from "../utils/httpClient";
import { config } from "../../src/config";
let dbVersion: number; let dbVersion: number;
describe("getStatus", () => { describe("getStatus", () => {
@@ -86,4 +87,27 @@ describe("getStatus", () => {
}) })
.catch(err => done(err)); .catch(err => done(err));
}); });
it("Should be able to get statusRequests only", function (done) {
if (!config.redis) this.skip();
client.get(`${endpoint}/statusRequests`)
.then(res => {
assert.strictEqual(res.status, 200);
assert.ok(Number(res.data) > 1);
done();
})
.catch(err => done(err));
});
it("Should be able to get status with statusRequests", function (done) {
if (!config.redis) this.skip();
client.get(endpoint)
.then(res => {
assert.strictEqual(res.status, 200);
const data = res.data;
assert.ok(data.statusRequests > 2);
done();
})
.catch(err => done(err));
});
}); });

165
test/cases/tempVip.ts Normal file
View File

@@ -0,0 +1,165 @@
import { config } from "../../src/config";
import { getHash } from "../../src/utils/getHash";
import { tempVIPKey } from "../../src/utils/redisKeys";
import { HashedUserID } from "../../src/types/user.model";
import { client } from "../utils/httpClient";
import { db, privateDB } from "../../src/databases/databases";
import redis from "../../src/utils/redis";
import assert from "assert";
// helpers
const getSegment = (UUID: string) => db.prepare("get", `SELECT "votes", "locked", "category" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]);
const permVIP = "tempVipPermOne";
const publicPermVIP = getHash(permVIP) as HashedUserID;
const tempVIPOne = "tempVipTempOne";
const publicTempVIPOne = getHash(tempVIPOne) as HashedUserID;
const UUID0 = "tempvip-uuid0";
const UUID1 = "tempvip-uuid1";
const tempVIPEndpoint = "/api/addUserAsTempVIP";
const addTempVIP = (enabled: boolean) => client({
url: tempVIPEndpoint,
method: "POST",
params: {
userID: publicTempVIPOne,
adminUserID: permVIP,
channelVideoID: "channelid-convert",
enabled: enabled
}
});
const voteEndpoint = "/api/voteOnSponsorTime";
const postVote = (userID: string, UUID: string, type: number) => client({
method: "POST",
url: voteEndpoint,
params: {
userID,
UUID,
type
}
});
const postVoteCategory = (userID: string, UUID: string, category: string) => client({
method: "POST",
url: voteEndpoint,
params: {
userID,
UUID,
category
}
});
const checkUserVIP = async () => {
const { reply } = await redis.getAsync(tempVIPKey(publicTempVIPOne));
return reply;
};
describe("tempVIP test", function() {
before(async function() {
if (!config.redis) this.skip();
const insertSponsorTimeQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "shadowHidden") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
await db.prepare("run", insertSponsorTimeQuery, ["channelid-convert", 0, 1, 0, 0, UUID0, "testman", 0, 50, "sponsor", 0]);
await db.prepare("run", insertSponsorTimeQuery, ["channelid-convert", 1, 9, 0, 1, "tempvip-submit", publicTempVIPOne, 0, 50, "sponsor", 0]);
await db.prepare("run", insertSponsorTimeQuery, ["otherchannel", 1, 9, 0, 1, UUID1, "testman", 0, 50, "sponsor", 0]);
await db.prepare("run", 'INSERT INTO "vipUsers" ("userID") VALUES (?)', [publicPermVIP]);
// clear redis if running consecutive tests
await redis.delAsync(tempVIPKey(publicTempVIPOne));
});
it("Should update db version when starting the application", () => {
privateDB.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])
.then(row => {
assert.ok(row.value >= 5, `Versions are not at least 5. private is ${row.value}`);
});
});
it("User should not already be temp VIP", (done) => {
checkUserVIP()
.then(result => {
assert.ok(!result);
done(result);
})
.catch(err => done(err));
});
it("Should be able to normal upvote as a user", (done) => {
postVote(tempVIPOne, UUID0, 1)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, 1);
done();
})
.catch(err => done(err));
});
it("Should be able to add tempVIP", (done) => {
addTempVIP(true)
.then(async res => {
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP();
assert.ok(vip == "ChannelID");
done();
})
.catch(err => done(err));
});
it("Should be able to VIP downvote", (done) => {
postVote(tempVIPOne, UUID0, 0)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.votes, -2);
done();
})
.catch(err => done(err));
});
it("Should be able to VIP lock", (done) => {
postVote(tempVIPOne, UUID0, 1)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.ok(row.votes > -2);
assert.strictEqual(row.locked, 1);
done();
})
.catch(err => done(err));
});
it("Should be able to VIP change category", (done) => {
postVoteCategory(tempVIPOne, UUID0, "filler")
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID0);
assert.strictEqual(row.category, "filler");
assert.strictEqual(row.locked, 1);
done();
})
.catch(err => done(err));
});
it("Should be able to remove tempVIP prematurely", (done) => {
addTempVIP(false)
.then(async res => {
assert.strictEqual(res.status, 200);
const vip = await checkUserVIP();
done(vip);
})
.catch(err => done(err));
});
it("Should not be able to VIP downvote", (done) => {
postVote(tempVIPOne, UUID1, 0)
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.votes, 0);
done();
})
.catch(err => done(err));
});
it("Should not be able to VIP change category", (done) => {
postVoteCategory(tempVIPOne, UUID1, "filler")
.then(async res => {
assert.strictEqual(res.status, 200);
const row = await getSegment(UUID1);
assert.strictEqual(row.category, "sponsor");
done();
})
.catch(err => done(err));
});
});

View File

@@ -47,6 +47,15 @@ export class YouTubeApiMock {
] ]
} as APIVideoData } as APIVideoData
}; };
} else if (obj.id === "channelid-convert") {
return {
err: null,
data: {
title: "Video Lookup Title",
author: "ChannelAuthor",
authorId: "ChannelID"
} as APIVideoData
};
} else { } else {
return { return {
err: null, err: null,