diff --git a/db/05-config.sql b/db/05-config.sql index 1c4b65a..2e830ab 100644 --- a/db/05-config.sql +++ b/db/05-config.sql @@ -1,12 +1,14 @@ CREATE TABLE config ( rowlock bool DEFAULT TRUE UNIQUE NOT NULL CHECK (rowlock = TRUE), - normaluservotepoints int NOT NULL, - followervotepoints int NOT NULL, - subscribervotepoints int NOT NULL, - title varchar NOT NULL, - colors jsonb NOT NULL, + normaluservotepoints int NOT NULL DEFAULT 10, + followervotepoints int NOT NULL DEFAULT 50, + subscribervotepoints int NOT NULL DEFAULT 100, + normaluserratelimit int NOT NULL DEFAULT 1, + followerratelimit int NOT NULL DEFAULT 2, + subscriberratelimit int NOT NULL DEFAULT 3, + title varchar NOT NULL DEFAULT '{username}''s Learn Request Queue', + colors jsonb NOT NULL DEFAULT '{"bg": {"primary": "#444444","table": "#282828","navbar": "#666666","error": "#ff0000"},"fg": {"primary": "#dddddd","ahover": "#ffffff","title": "#eeeeee"}}', PRIMARY KEY (rowLock) ); -INSERT INTO config (normalUserVotePoints,followerVotePoints,subscriberVotePoints,title,colors) - VALUES (10,50,100,'{username}''s Learn Request Queue','{"bg": {"primary": "#444444","table": "#282828","navbar": "#666666","error": "#ff0000"},"fg": {"primary": "#dddddd","ahover": "#ffffff","title": "#eeeeee"}}'); +INSERT INTO config (rowlock) VALUES (true); diff --git a/db/90-views.sql b/db/90-views.sql index 14896b0..a31ec16 100644 --- a/db/90-views.sql +++ b/db/90-views.sql @@ -60,3 +60,22 @@ CREATE OR REPLACE VIEW streamer_user_vw AS SELECT users.userid as userid, users.displayname as displayname, users.imageurl as imageurl FROM streamer LEFT JOIN users ON streamer.userid = users.userid; + +CREATE OR REPLACE VIEW ratelimit_vw AS + SELECT users.userid,COALESCE(count,0),ratelimit.reqcount AS max,COALESCE(count,0) >= ratelimit.reqcount AS status + FROM users + LEFT JOIN (SELECT requester,COUNT(url) + FROM requests + WHERE reqtimestamp > (now() - '24 hours'::interval) + GROUP BY requests.requester + ) AS requests ON users.userid = requests.requester + LEFT JOIN follows ON requests.requester = follows.userid + LEFT JOIN subscriptions ON requests.requester = subscriptions.userid + CROSS JOIN config + CROSS JOIN LATERAL (VALUES ( + CASE + WHEN follows.userid IS NULL AND subscriptions.userid IS NULL THEN config.normaluserratelimit + WHEN follows.userid IS NOT NULL AND subscriptions.userid IS NULL THEN config.followerratelimit + WHEN subscriptions.userid IS NOT NULL THEN config.subscriberratelimit + END + )) AS ratelimit(reqcount); diff --git a/db/upgrade/v0.7-v0.8.sql b/db/upgrade/v0.7-v0.8.sql new file mode 100644 index 0000000..c5d5ab7 --- /dev/null +++ b/db/upgrade/v0.7-v0.8.sql @@ -0,0 +1,34 @@ +BEGIN; + +ALTER TABLE config + ALTER COLUMN normaluservotepoints SET DEFAULT 10, + ALTER COLUMN followervotepoints SET DEFAULT 50, + ALTER COLUMN subscribervotepoints SET DEFAULT 100, + ALTER COLUMN title SET DEFAULT '{username}''s Learn Request Queue', + ALTER COLUMN colors SET DEFAULT '{"bg": {"primary": "#444444","table": "#282828","navbar": "#666666","error": "#ff0000"},"fg": {"primary": "#dddddd","ahover": "#ffffff","title": "#eeeeee"}}', + ADD COLUMN normaluserratelimit int NOT NULL DEFAULT 1, + ADD COLUMN followerratelimit int NOT NULL DEFAULT 2, + ADD COLUMN subscriberratelimit int NOT NULL DEFAULT 3; + +CREATE OR REPLACE VIEW ratelimit_vw AS + SELECT users.userid,COALESCE(count,0),ratelimit.reqcount AS max,COALESCE(count,0) >= ratelimit.reqcount AS status + FROM users + LEFT JOIN (SELECT requester,COUNT(url) + FROM requests + WHERE reqtimestamp > (now() - '24 hours'::interval) + GROUP BY requests.requester + ) AS requests ON users.userid = requests.requester + LEFT JOIN follows ON requests.requester = follows.userid + LEFT JOIN subscriptions ON requests.requester = subscriptions.userid + CROSS JOIN config + CROSS JOIN LATERAL (VALUES ( + CASE + WHEN follows.userid IS NULL AND subscriptions.userid IS NULL THEN config.normaluserratelimit + WHEN follows.userid IS NOT NULL AND subscriptions.userid IS NULL THEN config.followerratelimit + WHEN subscriptions.userid IS NOT NULL THEN config.subscriberratelimit + END + )) AS ratelimit(reqcount); + + +ROLLBACK; +--COMMIT; diff --git a/src/queries.ts b/src/queries.ts index 09c615b..8fa54e6 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -130,3 +130,8 @@ export const updateCronJobLastSuccess = { name: "updateCronJobLastSuccess", text: "UPDATE cron SET lastSuccess = now() WHERE jobName = $1" } + +export const getRateLimitStatus = { + name: "getRateLimitStatus", + text: "SELECT * FROM ratelimit_vw WHERE userid = $1" +} diff --git a/src/requests.ts b/src/requests.ts index 0033526..1a35094 100644 --- a/src/requests.ts +++ b/src/requests.ts @@ -87,6 +87,7 @@ async function retrieveYoutubeMetadata(url: string) { } export async function addRequest(url: string, requester: string): Promise<[number,string]> { + // Check that URL is of an accepted format var validUrl = false; for (var regex of validUrlRegexes) { if (regex.test(url)) { @@ -95,11 +96,24 @@ export async function addRequest(url: string, requester: string): Promise<[numbe } } if (!validUrl) return [400, "Invalid song URL."]; - var result = await checkRequestExists(url) - if (result) { - console.log(result); - return [200,`Song already requested by ${result.rows[0].requester}. State: ${result.rows[0].state}`] + + // Check whether the URL has already been requested + var existsResult = await checkRequestExists(url) + if (existsResult) { + console.log(existsResult); + return [200,`Song already requested by ${existsResult.rows[0].requester}. State: ${existsResult.rows[0].state}`] } + + // Check whether the user has hit their rate limit + var rateLimitQuery = Object.assign(queries.getRateLimitStatus, { values: [requester] }); + var rateLimitResult = (await db.query(rateLimitQuery)).rows[0]; + if (rateLimitResult.status) { + return [429,`You have reached your maximum of ${rateLimitResult.max} requests per day. + Please try again later.\n + Tip: Removing one of your requests from the past 24 hours by retracting your vote will allow you to replace it with another.`]; + } + + // Add the request var query = Object.assign(queries.addRequest, { values: [url,requester] }); return db.query(query) .then(async () => {