Compare commits
32 commits
Author | SHA1 | Date | |
---|---|---|---|
dd423bdb34 | |||
ac953b0bb5 | |||
90f672b288 | |||
8b49d51e08 | |||
7897fe6f3d | |||
6e26650077 | |||
891ccfae92 | |||
fc09fd4dd4 | |||
347a6d2324 | |||
632dc73ee9 | |||
8483705ae4 | |||
5e42236354 | |||
6782ad2fb4 | |||
e4910f87d1 | |||
28d5ce09dd | |||
b968f054d5 | |||
84a22ccffd | |||
c634e763f3 | |||
4607eaf307 | |||
354eddb673 | |||
6bc292f122 | |||
2a87e0408e | |||
dbe7e4c52d | |||
60defb7ea6 | |||
fcd8f2b197 | |||
ad435e0dba | |||
d2cf4aa1d2 | |||
6495e1c8ef | |||
1c34b3f013 | |||
2e5762c029 | |||
7a4c39353c | |||
57991dc36a |
33 changed files with 1048 additions and 145 deletions
30
RELEASE.md
Normal file
30
RELEASE.md
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Release Checklist
|
||||
|
||||
## Release Type
|
||||
- Were there any database schema changes?
|
||||
- Are there any significant UI changes?
|
||||
- Have any significant new features been added?
|
||||
- Are there any API changes?
|
||||
|
||||
If the answer to any of the above is yes, then the release MUST be a major or minor release. Otherwise, the release MAY be a patch release (at developer's discretion).
|
||||
|
||||
## Major/Minor Releases
|
||||
- [ ] Add a commit which adds a database upgrade script at `db/upgrade/[oldversion]-[newversion].sql`
|
||||
- ALWAYS use a transaction for the entirety of this file
|
||||
- At minimum, DB version must be bumped
|
||||
- Test the upgrade script as follows:
|
||||
```
|
||||
git checkout [previous release tag]
|
||||
docker-compose down
|
||||
docker-compose up
|
||||
psql -h 0 -U postgres < db/upgrade/v[previous]-v[current].sql
|
||||
```
|
||||
Update the version in `src/version.ts` and verify the app works as expected.
|
||||
- [ ] Add a commit which bumps the version in `src/version.ts` and `db/00-version.sql`, entitled `Bump version to vMAJOR.MINOR`
|
||||
- [ ] Tag the latest commit with `vMAJOR.MINOR`
|
||||
- [ ] Write release notes
|
||||
|
||||
## Patch Releases
|
||||
- [ ] Add a commit which bumps the patch level in `src/version.ts`, entitled `Bump version to vMAJOR.MINOR.PATCH`
|
||||
- [ ] Tag the latest commit with `vMAJOR.MINOR.PATCH`
|
||||
- [ ] Write release notes
|
|
@ -8,4 +8,4 @@ CREATE OR REPLACE FUNCTION get_version() RETURNS VARCHAR
|
|||
AS $$SELECT major || '.' || minor FROM version $$
|
||||
LANGUAGE SQL;
|
||||
|
||||
INSERT INTO version (major,minor) VALUES (0,2);
|
||||
INSERT INTO version (major,minor) VALUES (0,8);
|
||||
|
|
|
@ -1,10 +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,
|
||||
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)
|
||||
VALUES (10,50,100);
|
||||
INSERT INTO config (rowlock) VALUES (true);
|
||||
|
|
|
@ -8,4 +8,6 @@ CREATE TABLE cron (
|
|||
|
||||
INSERT INTO cron (jobName,runInterval) VALUES
|
||||
('processBans','30 minutes'),
|
||||
('processFollows','30 minutes'),
|
||||
('processSubscriptions','30 minutes'),
|
||||
('processEmptyMetadata','1 day');
|
||||
|
|
|
@ -2,7 +2,5 @@ CREATE TABLE users (
|
|||
userId int NOT NULL,
|
||||
displayName varchar NOT NULL,
|
||||
imageUrl varchar,
|
||||
isFollower boolean NOT NULL DEFAULT FALSE,
|
||||
isSubscriber boolean NOT NULL DEFAULT FALSE,
|
||||
PRIMARY KEY (userId)
|
||||
);
|
||||
|
|
|
@ -3,4 +3,4 @@ CREATE TABLE streamer (
|
|||
tokenPair json,
|
||||
PRIMARY KEY (userid),
|
||||
FOREIGN KEY (userid) REFERENCES users(userid)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
CREATE TABLE requests (
|
||||
url varchar NOT NULL UNIQUE,
|
||||
url varchar NOT NULL,
|
||||
requester int NOT NULL,
|
||||
state varchar NOT NULL DEFAULT 'Requested',
|
||||
reqTimestamp timestamptz NOT NULL DEFAULT NOW(),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
CREATE TABLE votes (
|
||||
requestUrl varchar,
|
||||
userId int,
|
||||
requestUrl varchar NOT NULL,
|
||||
userId int NOT NULL,
|
||||
PRIMARY KEY (requestUrl,userId),
|
||||
FOREIGN KEY (requestUrl) REFERENCES requests(url) ON DELETE CASCADE,
|
||||
FOREIGN KEY (userId) REFERENCES users(userId) ON DELETE CASCADE
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
CREATE TABLE bans (
|
||||
userid integer,
|
||||
PRIMARY KEY (userid)
|
||||
)
|
||||
);
|
||||
|
|
4
db/50-follows.sql
Normal file
4
db/50-follows.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE follows (
|
||||
userid integer,
|
||||
PRIMARY KEY (userid)
|
||||
);
|
4
db/50-subscriptions.sql
Normal file
4
db/50-subscriptions.sql
Normal file
|
@ -0,0 +1,4 @@
|
|||
CREATE TABLE subscriptions (
|
||||
userid integer,
|
||||
PRIMARY KEY (userid)
|
||||
);
|
|
@ -2,8 +2,7 @@ CREATE OR REPLACE VIEW requests_vw AS
|
|||
SELECT url,COALESCE(videoTitle,url) AS title,displayName AS requester,imageUrl,state,score,reqTimestamp FROM requests
|
||||
JOIN requestMetadata USING (url)
|
||||
JOIN scores USING (url)
|
||||
JOIN users ON requests.requester = users.userid
|
||||
ORDER BY score DESC, reqTimestamp ASC;
|
||||
JOIN users ON requests.requester = users.userid;
|
||||
|
||||
CREATE OR REPLACE FUNCTION get_requests_voted(votinguserid int)
|
||||
RETURNS TABLE (
|
||||
|
@ -21,8 +20,7 @@ CREATE OR REPLACE FUNCTION get_requests_voted(votinguserid int)
|
|||
SELECT url,title,requester,imageUrl,state,score,reqTimestamp,
|
||||
(CASE WHEN votes.userid IS NULL THEN FALSE ELSE TRUE END) AS voted
|
||||
FROM requests_vw
|
||||
LEFT JOIN votes ON (requests_vw.url = votes.requesturl AND votes.userid = votinguserid)
|
||||
ORDER BY score DESC, reqTimestamp ASC;
|
||||
LEFT JOIN votes ON (requests_vw.url = votes.requesturl AND votes.userid = votinguserid);
|
||||
$$;
|
||||
|
||||
/*
|
||||
|
@ -40,18 +38,20 @@ CREATE OR REPLACE VIEW vote_score_vw AS
|
|||
COUNT(votes.requesturl) AS count,
|
||||
COALESCE(
|
||||
SUM(CASE
|
||||
WHEN users.isfollower = FALSE AND users.issubscriber = FALSE
|
||||
WHEN follows.userid IS NULL AND subscriptions.userid IS NULL
|
||||
AND votes.userid IS NOT NULL
|
||||
THEN votepoints.normaluservotepoints
|
||||
WHEN users.isfollower = TRUE AND users.issubscriber = FALSE
|
||||
WHEN follows.userid IS NOT NULL AND subscriptions.userid IS NULL
|
||||
THEN votepoints.followervotepoints
|
||||
WHEN users.issubscriber = TRUE
|
||||
WHEN subscriptions.userid IS NOT NULL
|
||||
THEN votepoints.subscribervotepoints
|
||||
END), 0
|
||||
) AS votescore
|
||||
FROM requests
|
||||
LEFT JOIN votes ON votes.requesturl = requests.url
|
||||
LEFT JOIN users on votes.userid = users.userid
|
||||
LEFT JOIN bans ON users.userid = bans.userid
|
||||
LEFT JOIN bans ON votes.userid = bans.userid
|
||||
LEFT JOIN follows ON votes.userid = follows.userid
|
||||
LEFT JOIN subscriptions ON votes.userid = subscriptions.userid
|
||||
CROSS JOIN votepoints
|
||||
WHERE bans.userid IS NULL
|
||||
GROUP BY url;
|
||||
|
@ -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);
|
||||
|
|
|
@ -16,6 +16,13 @@ CREATE OR REPLACE PROCEDURE add_request(url varchar,requester int)
|
|||
CALL update_scores();
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE clear_zero_votes()
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
DELETE FROM requests WHERE NOT EXISTS
|
||||
(SELECT FROM votes WHERE requests.url = votes.requesturl);
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE add_vote(url varchar,voteuser int)
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
|
@ -28,6 +35,7 @@ CREATE OR REPLACE PROCEDURE delete_vote(url varchar,voteuser int)
|
|||
AS $$
|
||||
DELETE FROM votes WHERE requesturl = url AND userid = voteuser;
|
||||
CALL update_scores();
|
||||
CALL clear_zero_votes();
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE update_request_score_modifier(updateurl varchar, scoreDiff int)
|
||||
|
@ -36,3 +44,11 @@ CREATE OR REPLACE PROCEDURE update_request_score_modifier(updateurl varchar, sco
|
|||
UPDATE scores SET scoreModifier = scoreModifier + scoreDiff WHERE url = updateurl;
|
||||
CALL update_scores();
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE update_vote_points(normaluser int, follower int, subscriber int)
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
UPDATE config SET normaluservotepoints = normaluser,
|
||||
followervotepoints = follower, subscribervotepoints = subscriber;
|
||||
CALL update_scores();
|
||||
$$;
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
INSERT INTO users (userid,displayName,isFollower,isSubscriber) VALUES
|
||||
(001,'TestUser',false,false),
|
||||
(002,'TestFollower',true,false),
|
||||
(003,'TestSubscriber',true,true),
|
||||
(004,'TestSubNonFollower',false,true);
|
||||
INSERT INTO users (userid,displayName) VALUES
|
||||
(001,'TestUser'),
|
||||
(002,'TestFollower'),
|
||||
(003,'TestSubscriber'),
|
||||
(004,'TestSubNonFollower');
|
||||
|
||||
INSERT INTO follows (userid) VALUES
|
||||
(002),
|
||||
(003);
|
||||
|
||||
INSERT INTO subscriptions (userid) VALUES
|
||||
(003),
|
||||
(004);
|
||||
|
||||
CALL add_request('https://www.youtube.com/watch?v=dQw4w9WgXcQ',001);
|
||||
CALL add_request('https://www.youtube.com/watch?v=C5oeWHngDS4',002);
|
||||
|
|
9
db/upgrade/v0.3-0.4.sql
Normal file
9
db/upgrade/v0.3-0.4.sql
Normal file
|
@ -0,0 +1,9 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE version SET minor = 4;
|
||||
|
||||
ALTER TABLE config ADD COLUMN title varchar, ADD COLUMN colors jsonb;
|
||||
UPDATE config SET title = '{username}''s Learn Request Queue', colors = '{"bg": {"primary": "#444444","table": "#282828","navbar" : "#666666","error": "#ff0000"},"fg": {"primary": "#dddddd","ahover": "#ffffff","title": "#eeeeee"}}';
|
||||
ALTER TABLE config ALTER COLUMN title SET NOT NULL, ALTER COLUMN colors SET NOT NULL;
|
||||
|
||||
COMMIT;
|
53
db/upgrade/v0.4-0.5.sql
Normal file
53
db/upgrade/v0.4-0.5.sql
Normal file
|
@ -0,0 +1,53 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE version SET minor = 5;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE update_vote_points(normaluser int, follower int, subscriber int)
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
UPDATE config SET normaluservotepoints = normaluser,
|
||||
followervotepoints = follower, subscribervotepoints = subscriber;
|
||||
CALL update_scores();
|
||||
$$;
|
||||
|
||||
CREATE TABLE follows (
|
||||
userid integer,
|
||||
PRIMARY KEY (userid)
|
||||
);
|
||||
|
||||
CREATE TABLE subscriptions (
|
||||
userid integer,
|
||||
PRIMARY KEY (userid)
|
||||
);
|
||||
|
||||
CREATE OR REPLACE VIEW vote_score_vw AS
|
||||
WITH votepoints AS (SELECT normaluservotepoints, followervotepoints, subscribervotepoints FROM config)
|
||||
SELECT requests.url AS url,
|
||||
COUNT(votes.requesturl) AS count,
|
||||
COALESCE(
|
||||
SUM(CASE
|
||||
WHEN follows.userid IS NULL AND subscriptions.userid IS NULL
|
||||
AND votes.userid IS NOT NULL
|
||||
THEN votepoints.normaluservotepoints
|
||||
WHEN follows.userid IS NOT NULL AND subscriptions.userid IS NULL
|
||||
THEN votepoints.followervotepoints
|
||||
WHEN subscriptions.userid IS NOT NULL
|
||||
THEN votepoints.subscribervotepoints
|
||||
END), 0
|
||||
) AS votescore
|
||||
FROM requests
|
||||
LEFT JOIN votes ON votes.requesturl = requests.url
|
||||
LEFT JOIN bans ON votes.userid = bans.userid
|
||||
LEFT JOIN follows ON votes.userid = follows.userid
|
||||
LEFT JOIN subscriptions ON votes.userid = subscriptions.userid
|
||||
CROSS JOIN votepoints
|
||||
WHERE bans.userid IS NULL
|
||||
GROUP BY url;
|
||||
|
||||
ALTER TABLE users DROP COLUMN isfollower, DROP COLUMN issubscriber;
|
||||
|
||||
INSERT INTO cron (jobName,runInterval) VALUES
|
||||
('processFollows','30 minutes'),
|
||||
('processSubscriptions','30 minutes');
|
||||
|
||||
COMMIT;
|
5
db/upgrade/v0.5-v0.6.sql
Normal file
5
db/upgrade/v0.5-v0.6.sql
Normal file
|
@ -0,0 +1,5 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE version SET minor = 6;
|
||||
|
||||
COMMIT;
|
24
db/upgrade/v0.6-v0.7.sql
Normal file
24
db/upgrade/v0.6-v0.7.sql
Normal file
|
@ -0,0 +1,24 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE version SET minor = 7;
|
||||
|
||||
ALTER TABLE votes
|
||||
ALTER COLUMN requestUrl SET NOT NULL,
|
||||
ALTER COLUMN userId SET NOT NULL;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE clear_zero_votes()
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
DELETE FROM requests WHERE NOT EXISTS
|
||||
(SELECT FROM votes WHERE requests.url = votes.requesturl);
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE delete_vote(url varchar,voteuser int)
|
||||
LANGUAGE SQL
|
||||
AS $$
|
||||
DELETE FROM votes WHERE requesturl = url AND userid = voteuser;
|
||||
CALL update_scores();
|
||||
CALL clear_zero_votes();
|
||||
$$;
|
||||
|
||||
COMMIT;
|
35
db/upgrade/v0.7-v0.8.sql
Normal file
35
db/upgrade/v0.7-v0.8.sql
Normal file
|
@ -0,0 +1,35 @@
|
|||
BEGIN;
|
||||
|
||||
UPDATE version SET minor = 8;
|
||||
|
||||
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);
|
||||
|
||||
|
||||
COMMIT;
|
202
public/main.js
202
public/main.js
|
@ -1,28 +1,52 @@
|
|||
var requestsDiv = document.getElementById("requests");
|
||||
var cronJobs = ['processBans'];
|
||||
var currentPage = 1;
|
||||
var totalPages = 1;
|
||||
var count = document.getElementById("count").value;
|
||||
var sortBy = document.getElementById("sortBy").value;
|
||||
var sortDir = "desc";
|
||||
|
||||
function getRequests(count,allRequests) {
|
||||
var reqUrl;
|
||||
if (allRequests) {
|
||||
reqUrl = "/api/getAllRequests";
|
||||
} else {
|
||||
reqUrl = "/api/getRequests";
|
||||
}
|
||||
reqUrl += `?count=${count}`;
|
||||
function getRequests(offset,allRequests) {
|
||||
if (allRequests) var reqUrl = "/api/getAllRequests";
|
||||
else var reqUrl = "/api/getRequests";
|
||||
reqUrl += `?count=${count}&offset=${offset}&sort=${sortBy}&sortDirection=${sortDir}`;
|
||||
fetch(reqUrl)
|
||||
.then(response => response.json())
|
||||
.then(requests => {
|
||||
window.requests = requests;
|
||||
buildTable();
|
||||
buildTable(requests);
|
||||
});
|
||||
}
|
||||
|
||||
function buildTable() {
|
||||
var requestsDivHTML = '<table><tr><th class="request-link">Song</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
|
||||
function buildTable(requests) {
|
||||
totalPages = Math.ceil(requests.total/document.getElementById("count").value);
|
||||
document.getElementById("totalPages").innerText = totalPages;
|
||||
if (currentPage <= 1) {
|
||||
currentPage = 1;
|
||||
document.getElementById("pageBtnFirst").disabled = true;
|
||||
document.getElementById("pageBtnPrev").disabled = true;
|
||||
} else {
|
||||
document.getElementById("pageBtnFirst").disabled = false;
|
||||
document.getElementById("pageBtnPrev").disabled = false;
|
||||
}
|
||||
if (currentPage >= totalPages) {
|
||||
currentPage = totalPages;
|
||||
document.getElementById("pageBtnLast").disabled = true;
|
||||
document.getElementById("pageBtnNext").disabled = true;
|
||||
} else {
|
||||
document.getElementById("pageBtnLast").disabled = false;
|
||||
document.getElementById("pageBtnNext").disabled = false;
|
||||
}
|
||||
document.getElementById("page").innerHTML = "";
|
||||
for (i = 1; i <= totalPages; i++) {
|
||||
document.getElementById("page").innerHTML += `<option value=${i}>${i}</option>`;
|
||||
}
|
||||
document.getElementById("page").value = currentPage;
|
||||
var requestsDivHTML = '<table><tr><th class="request-link">Song Title</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
|
||||
requestsDivHTML += '<th class="request-state">State</td>';
|
||||
if (window.loggedIn) requestsDivHTML += '<th class="request-vote">Vote</td>';
|
||||
if (window.isStreamer) requestsDivHTML += '<th class="request-update">Update</th>'
|
||||
requestsDivHTML += "</tr>";
|
||||
for (request of requests) {
|
||||
for (request of requests.requests) {
|
||||
requestsDivHTML += `<tr><td class="request-link"><a href="${request.url}" target="_blank">${request.title}</a></td>\
|
||||
<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
|
||||
<td class="request-score">${request.score}</td>`;
|
||||
|
@ -44,12 +68,14 @@ function buildTable() {
|
|||
}
|
||||
|
||||
function updateTable() {
|
||||
allRequests = document.getElementById("allRequests").checked;
|
||||
getRequests(document.getElementById("count").value,allRequests);
|
||||
var offset = (currentPage - 1) * count;
|
||||
var allRequests = document.getElementById("allRequests").checked;
|
||||
getRequests(offset,allRequests);
|
||||
}
|
||||
|
||||
function applyUrlTransforms(url) {
|
||||
console.log("Begin applyUrlTransforms:" + url);
|
||||
url = url.trim();
|
||||
if (url.match(/^https?:\/\/(www\.)?youtu(\.be|be\.com)\//)) { // Youtube
|
||||
console.log("Youtube");
|
||||
var videoid = "";
|
||||
|
@ -65,6 +91,27 @@ function applyUrlTransforms(url) {
|
|||
}
|
||||
}
|
||||
|
||||
function goToPage(page) {
|
||||
currentPage = parseInt(page,10);
|
||||
updateTable();
|
||||
}
|
||||
|
||||
function getColorObject() {
|
||||
return {
|
||||
bg: {
|
||||
primary: document.getElementById('color-bg-primary').value,
|
||||
table: document.getElementById('color-bg-table').value,
|
||||
navbar: document.getElementById('color-bg-navbar').value,
|
||||
error: document.getElementById('color-bg-error').value,
|
||||
},
|
||||
fg: {
|
||||
primary: document.getElementById('color-fg-primary').value,
|
||||
ahover: document.getElementById('color-fg-ahover').value,
|
||||
title: document.getElementById('color-fg-title').value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addRequestErr(msg) {
|
||||
document.getElementById('addRequestError').style.display = "inline-block";
|
||||
document.getElementById('addRequestError').innerText = msg;
|
||||
|
@ -85,33 +132,46 @@ function updateRequestErrReset() {
|
|||
document.getElementById('updateRequestError').innerText = "";
|
||||
}
|
||||
|
||||
function showMessage(msg) {
|
||||
document.getElementById("messageModalText").innerText = msg;
|
||||
function streamerSettingsErr(msg) {
|
||||
document.getElementById('streamerSettingsError').style.display = "inline-block";
|
||||
document.getElementById('streamerSettingsError').innerText = msg;
|
||||
}
|
||||
|
||||
function streamerSettingsErrReset() {
|
||||
document.getElementById('streamerSettingsError').style.display = "none";
|
||||
document.getElementById('streamerSettingsError').innerText = "";
|
||||
}
|
||||
|
||||
// Hides all modals in preparation to show a one or to close out of
|
||||
// all modals and return to the page. Does NOT hide modalBackground.
|
||||
function hideModals() {
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
document.getElementById("addRequestModal").style.display = "none";
|
||||
document.getElementById("updateRequestModal").style.display = "none";
|
||||
document.getElementById("deleteRequestModal").style.display = "none";
|
||||
document.getElementById("streamerSettingsModal").style.display = "none";
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
hideModals();
|
||||
document.getElementById("modalBackground").style.display = "none";
|
||||
}
|
||||
|
||||
function showMessage(msg) {
|
||||
hideModals();
|
||||
document.getElementById("messageModalText").innerText = msg;
|
||||
document.getElementById("modalBackground").style.display = "flex";
|
||||
document.getElementById("messageModal").style.display = "block";
|
||||
}
|
||||
|
||||
function closeMessageModal() {
|
||||
document.getElementById("modalBackground").style.display = "none";
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
}
|
||||
|
||||
function openAddRequestModal() {
|
||||
hideModals();
|
||||
document.getElementById("modalBackground").style.display = "flex";
|
||||
document.getElementById("updateRequestModal").style.display = "none";
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
document.getElementById("addRequestModal").style.display = "block";
|
||||
}
|
||||
|
||||
function closeAddRequestModal() {
|
||||
document.getElementById("modalBackground").style.display = "none";
|
||||
document.getElementById("addRequestModal").style.display = "none";
|
||||
}
|
||||
|
||||
function openUpdateRequestModal(tr) {
|
||||
hideModals();
|
||||
var url = tr.getElementsByClassName('request-link')[0].firstChild.href;
|
||||
var score = tr.getElementsByClassName('request-score')[0].innerText;
|
||||
var state = tr.getElementsByClassName('request-state')[0].innerText;
|
||||
|
@ -120,18 +180,12 @@ function openUpdateRequestModal(tr) {
|
|||
document.getElementById("updateRequestModalCurrentScore").innerText = score;
|
||||
document.querySelector(`#updateRequestStateSelect [value="${state}"]`).selected = true;
|
||||
document.getElementById("scoreModifierInput").value = 0;
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
document.getElementById("addRequestModal").style.display = "none";
|
||||
document.getElementById("modalBackground").style.display = "flex";
|
||||
document.getElementById("updateRequestModal").style.display = "block";
|
||||
}
|
||||
|
||||
function closeUpdateRequestModal() {
|
||||
document.getElementById("modalBackground").style.display = "none";
|
||||
document.getElementById("updateRequestModal").style.display = "none";
|
||||
}
|
||||
|
||||
function openDeleteRequestModal(url) {
|
||||
hideModals();
|
||||
document.getElementById("updateRequestUrl").href = url;
|
||||
document.getElementById("updateRequestUrl").innerText = url;
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
|
@ -141,17 +195,21 @@ function openDeleteRequestModal(url) {
|
|||
document.getElementById("deleteRequestModal").style.display = "block";
|
||||
}
|
||||
|
||||
// Returns to update request modal
|
||||
function closeDeleteRequestModal() {
|
||||
hideModals();
|
||||
document.getElementById("deleteRequestModal").style.display = "none";
|
||||
document.getElementById("updateRequestModal").style.display = "block";
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
document.getElementById("messageModal").style.display = "none";
|
||||
document.getElementById("addRequestModal").style.display = "none";
|
||||
document.getElementById("updateRequestModal").style.display = "none";
|
||||
document.getElementById("deleteRequestModal").style.display = "none";
|
||||
document.getElementById("modalBackground").style.display = "none";
|
||||
function openStreamerSettingsModal() {
|
||||
hideModals();
|
||||
document.getElementById("modalBackground").style.display = "flex";
|
||||
document.getElementById("streamerSettingsModal").style.display = "block";
|
||||
}
|
||||
|
||||
function cronRequest(job) {
|
||||
if (!cronJobs.includes(job)) throw new Error("Request for invalid job");
|
||||
}
|
||||
|
||||
const validUrlRegexes = [
|
||||
|
@ -178,7 +236,7 @@ function validateAndSubmitRequest() {
|
|||
updateTable();
|
||||
document.getElementById("addRequestUrl").value = "";
|
||||
response.text().then((message) => {
|
||||
closeAddRequestModal();
|
||||
closeAllModals();
|
||||
showMessage(message);
|
||||
});
|
||||
});
|
||||
|
@ -262,14 +320,55 @@ function deleteRequest(url) {
|
|||
});
|
||||
}
|
||||
|
||||
function updatePageTitle(pageTitle) {
|
||||
streamerSettingsErrReset();
|
||||
fetch("/api/updatePageTitle", { method: 'POST', body: new URLSearchParams({
|
||||
pageTitle: pageTitle
|
||||
})})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
response.text().then(streamerSettingsErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateColors(colors) {
|
||||
streamerSettingsErrReset();
|
||||
fetch("/api/updateColors", { method: 'POST', headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}, body: JSON.stringify(colors)})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
response.text().then(streamerSettingsErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateVotePoints(user,follower,subscriber) {
|
||||
streamerSettingsErrReset();
|
||||
fetch("/api/updateVotePoints", { method: 'POST', body: new URLSearchParams({
|
||||
user: user,
|
||||
follower: follower,
|
||||
subscriber: subscriber
|
||||
})})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
location.reload();
|
||||
} else {
|
||||
response.text().then(streamerSettingsErr);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateTable();
|
||||
|
||||
document.addEventListener("keydown", function onEvent(event) {
|
||||
if (event.key === "Escape") {
|
||||
closeMessageModal();
|
||||
closeAddRequestModal();
|
||||
closeAllModals();
|
||||
}
|
||||
});
|
||||
document.getElementById("modalBackground").addEventListener("click", (e) => { if (e.target === e.currentTarget) closeAllModals();});
|
||||
|
@ -279,5 +378,16 @@ for(state of validStates) {
|
|||
var opt = document.createElement("option");
|
||||
opt.text = state;
|
||||
opt.value = state;
|
||||
updateRequestStateSelect.add(opt)
|
||||
updateRequestStateSelect.add(opt);
|
||||
}
|
||||
|
||||
function toggleSortDir() {
|
||||
if (window.sortDir == "desc") {
|
||||
document.getElementById("sortDir").innerText = "↑";
|
||||
window.sortDir = "asc";
|
||||
} else {
|
||||
document.getElementById("sortDir").innerText = "↓";
|
||||
window.sortDir = "desc";
|
||||
}
|
||||
updateTable();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,12 @@ button, input, select {
|
|||
font-size: 100%;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
border: none;
|
||||
padding: 0px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ddd;
|
||||
}
|
||||
|
@ -105,6 +111,20 @@ div#nav-userpic {
|
|||
text-align: right;
|
||||
}
|
||||
|
||||
.tableSettings {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tableSettings > span {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#tableSettingsTop {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#modalBackground {
|
||||
display: none;
|
||||
position: fixed;
|
||||
|
@ -117,6 +137,7 @@ div#nav-userpic {
|
|||
height: 100%;
|
||||
background-color: #444;
|
||||
background-color: #444a;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
@ -179,3 +200,18 @@ div#nav-userpic {
|
|||
#deleteRequestLink {
|
||||
color: #f00;
|
||||
}
|
||||
|
||||
#streamerSettingsMain {
|
||||
max-height: 65vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#votepoints {
|
||||
padding: 0 1.5em;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#sortDir {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
263
src/app.ts
263
src/app.ts
|
@ -30,6 +30,7 @@ const app = express();
|
|||
app.use(version.checkVersionMiddleware);
|
||||
app.use(express.static('public'));
|
||||
app.use(express.urlencoded({extended: false}));
|
||||
app.use(express.json());
|
||||
app.use(session({
|
||||
secret: config.sessionSecret,
|
||||
saveUninitialized: false,
|
||||
|
@ -41,31 +42,85 @@ app.use(session({
|
|||
|
||||
// API
|
||||
app.get("/api/getRequests", async (request, response) => {
|
||||
if (!request.session) {
|
||||
throw new Error ("Missing request.session")
|
||||
}
|
||||
var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
|
||||
if (!request.session) throw new Error ("Missing request.session");
|
||||
await validateApiToken(request.session);
|
||||
var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
|
||||
var requestOffset = ( request.query.offset ? parseInt(request.query.offset as string, 10) : 0 );
|
||||
var sortDirection = ( request.query.sortDirection == "asc" ? "ASC" : "DESC" );
|
||||
var inverseSortDirection = ( sortDirection == "ASC" ? "DESC" : "ASC" );
|
||||
switch (request.query.sort) {
|
||||
case undefined: // Default sort by newest
|
||||
case "timestamp":
|
||||
var requestSort = `reqTimestamp ${sortDirection}`;
|
||||
break;
|
||||
case "score":
|
||||
var requestSort = `score ${sortDirection}, reqTimestamp ${inverseSortDirection}`;
|
||||
break;
|
||||
case "title":
|
||||
var requestSort = `title ${sortDirection}`
|
||||
break;
|
||||
case "requester":
|
||||
var requestSort = `requester ${sortDirection}, title ${sortDirection}`
|
||||
break;
|
||||
default:
|
||||
response.status(400);
|
||||
response.send("Invalid sort");
|
||||
return;
|
||||
};
|
||||
var requestsTotal = await requests.getRequestsTotal();
|
||||
if (request.session.user) {
|
||||
requests.getRequestsVoted(requestCount,request.session.user.id).then((val: Array<any>) => response.send(val))
|
||||
requests.getRequestsVoted(requestCount,requestOffset,requestSort,request.session.user.id)
|
||||
.then((val: Array<any>) => response.send({
|
||||
total: requestsTotal,
|
||||
requests: val
|
||||
}))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
} else {
|
||||
requests.getRequests(requestCount).then((val: Array<any>) => response.send(val))
|
||||
requests.getRequests(requestCount,requestOffset,requestSort)
|
||||
.then((val: Array<any>) => response.send({
|
||||
total: requestsTotal,
|
||||
requests: val
|
||||
}))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/api/getAllRequests", async (request, response) => {
|
||||
if (!request.session) {
|
||||
throw new Error ("Missing request.session")
|
||||
}
|
||||
var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
|
||||
if (!request.session) throw new Error ("Missing request.session");
|
||||
await validateApiToken(request.session);
|
||||
var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
|
||||
var requestOffset = ( request.query.offset ? parseInt(request.query.offset as string, 10) : 0 );
|
||||
var sortDirection = ( request.query.sortDirection == "asc" ? "ASC" : "DESC" );
|
||||
switch (request.query.sort) {
|
||||
case undefined:
|
||||
case "score":
|
||||
var requestSort = `score ${sortDirection}, reqTimestamp ASC`;
|
||||
break;
|
||||
case "timestamp":
|
||||
var requestSort = `reqTimestamp ${sortDirection}`;
|
||||
break;
|
||||
case "alpha":
|
||||
var requestSort = `title ${sortDirection}`
|
||||
break;
|
||||
default:
|
||||
response.status(400);
|
||||
response.send("Invalid sort");
|
||||
return;
|
||||
}
|
||||
var requestsTotal = await requests.getAllRequestsTotal();
|
||||
if (request.session.user) {
|
||||
requests.getAllRequestsVoted(requestCount,request.session.user.id).then((val: Array<any>) => response.send(val))
|
||||
requests.getAllRequestsVoted(requestCount,requestOffset,requestSort,request.session.user.id)
|
||||
.then((val: Array<any>) => response.send({
|
||||
total: requestsTotal,
|
||||
requests: val
|
||||
}))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
} else {
|
||||
requests.getAllRequests(requestCount).then((val: Array<any>) => response.send(val))
|
||||
requests.getAllRequests(requestCount,requestOffset,requestSort)
|
||||
.then((val: Array<any>) => response.send({
|
||||
total: requestsTotal,
|
||||
requests: val
|
||||
}))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
}
|
||||
});
|
||||
|
@ -78,7 +133,8 @@ app.post("/api/addRequest", async (request, response) => {
|
|||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var banned = await db.query(Object.assign(queries.checkBan, { values: [request.session.user.id] })).then((result: pg.QueryResult) => result.rowCount > 0);
|
||||
var banned = await db.query(Object.assign(queries.checkBan, { values: [request.session.user.id] }))
|
||||
.then((result: pg.QueryResult) => result.rowCount > 0);
|
||||
if (banned) {
|
||||
response.status(401);
|
||||
response.send("You are banned; you may not add new requests.");
|
||||
|
@ -189,6 +245,139 @@ app.post("/api/updateRequestScoreModifier", async (request, response) => {
|
|||
.catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
app.post("/api/updatePageTitle", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.body.pageTitle) {
|
||||
response.status(400);
|
||||
response.send("Missing pageTitle");
|
||||
return;
|
||||
}
|
||||
var pageTitle = request.body.pageTitle as string;
|
||||
response.type('text/plain');
|
||||
pageTitle = pageTitle.replace(/[&<>"']/g, function(m) {
|
||||
switch (m) {
|
||||
case '&':
|
||||
return '&';
|
||||
case '<':
|
||||
return '<';
|
||||
case '>':
|
||||
return '>';
|
||||
case '"':
|
||||
return '"';
|
||||
case "'":
|
||||
return ''';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
});
|
||||
await db.query(Object.assign(queries.updatePageTitle,{ values: [pageTitle] }))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
response.status(200);
|
||||
response.send('Successfully updated page title');
|
||||
});
|
||||
|
||||
app.post("/api/updateColors", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.body.bg) {
|
||||
response.status(400);
|
||||
response.send("Missing bg");
|
||||
return;
|
||||
}
|
||||
if (!request.body.fg) {
|
||||
response.status(400);
|
||||
response.send("Missing fg");
|
||||
return;
|
||||
}
|
||||
type Colors = { [key: string]: { [key: string]: string} }
|
||||
var colors: Colors = { bg: {}, fg: {} };
|
||||
console.log(JSON.stringify(request.body,null,2));
|
||||
for (var color of ['primary','table','navbar','error']) {
|
||||
var setcolor = request.body.bg[color];
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(setcolor)) {
|
||||
colors.bg[color] = setcolor
|
||||
} else {
|
||||
response.status(400);
|
||||
response.send(`Color 'bg.${color}' missing or invalid`)
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (var color of ['primary','ahover','title']) {
|
||||
var setcolor = request.body.fg[color];
|
||||
if (/^#[0-9a-fA-F]{6}$/.test(setcolor)) {
|
||||
colors.fg[color] = setcolor
|
||||
} else {
|
||||
response.status(400);
|
||||
response.send(`Color 'fg.${color}' missing or invalid`)
|
||||
return;
|
||||
}
|
||||
}
|
||||
response.type('text/plain');
|
||||
await db.query(Object.assign(queries.updateColors,{ values: [JSON.stringify(colors)] }))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
response.status(200);
|
||||
response.send('Successfully updated colors');
|
||||
});
|
||||
|
||||
app.post("/api/updateVotePoints", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.body.user) {
|
||||
response.status(400);
|
||||
response.send("Missing user");
|
||||
return;
|
||||
}
|
||||
if (!request.body.follower) {
|
||||
response.status(400);
|
||||
response.send("Missing follower");
|
||||
return;
|
||||
}
|
||||
if (!request.body.subscriber) {
|
||||
response.status(400);
|
||||
response.send("Missing subscriber");
|
||||
return;
|
||||
}
|
||||
var user = request.body.user as number;
|
||||
var follower = request.body.follower as number;
|
||||
var subscriber = request.body.subscriber as number;
|
||||
response.type('text/plain');
|
||||
await db.query(Object.assign(queries.updateVotePoints,{ values: [user,follower,subscriber] }))
|
||||
.catch((e: any) => errorHandler(request,response,e));
|
||||
response.status(200);
|
||||
response.send('Successfully updated page title');
|
||||
});
|
||||
|
||||
app.post("/api/deleteRequest", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
|
@ -266,6 +455,37 @@ app.post("/api/deleteVote", async (request,response) => {
|
|||
.catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
app.get("/api/cronRequest", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
if (!request.session || !request.session.user) {
|
||||
response.status(401);
|
||||
response.send("Session expired; please log in again");
|
||||
return;
|
||||
}
|
||||
var streamerid = await db.query(queries.getStreamerId).then((result: pg.QueryResult) => result.rows[0]['userid']);
|
||||
if (request.session.user.id != streamerid) {
|
||||
response.status(401);
|
||||
response.send("You are not the streamer");
|
||||
return;
|
||||
}
|
||||
if (!request.query.job) {
|
||||
response.status(400);
|
||||
response.send("Missing job");
|
||||
return;
|
||||
}
|
||||
var job = request.body.job as string;
|
||||
try {
|
||||
cron.validateJob(job)
|
||||
} catch (e) {
|
||||
response.status(400);
|
||||
response.send("Invalid job")
|
||||
return;
|
||||
}
|
||||
response.type('text/plain');
|
||||
cron.request(job).catch((e: any) => errorHandler(request,response,e));
|
||||
});
|
||||
|
||||
|
||||
// Twitch callback
|
||||
app.get("/callback", async (request, response) => {
|
||||
if (request.query.error) {
|
||||
|
@ -302,7 +522,7 @@ app.get("/callback", async (request, response) => {
|
|||
app.get("/", async (request, response) => {
|
||||
if (request.session) await validateApiToken(request.session);
|
||||
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
|
||||
var validStates = JSON.stringify((await db.query(queries.getValidStates).then((result: pg.QueryResult) => result.rows)).map((row: any) => row.state));
|
||||
var streamerConfig = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]);
|
||||
if (typeof streamerInfo == 'undefined') {
|
||||
response.redirect(307, `https://id.twitch.tv/oauth2/authorize?client_id=${config.twitchClientId}&redirect_uri=${config.urlPrefix}/callback&response_type=code&scope=channel:read:subscriptions moderation:read`);
|
||||
return;
|
||||
|
@ -313,9 +533,11 @@ app.get("/", async (request, response) => {
|
|||
clientId: config.twitchClientId,
|
||||
urlPrefix: config.urlPrefix,
|
||||
streamerName: streamerInfo['displayname'],
|
||||
streamerProfilePicture: streamerInfo['imageurl']
|
||||
streamerProfilePicture: streamerInfo['imageurl'],
|
||||
config: streamerConfig,
|
||||
});
|
||||
} else {
|
||||
var validStates = JSON.stringify((await db.query(queries.getValidStates).then((result: pg.QueryResult) => result.rows)).map((row: any) => row.state));
|
||||
response.render('main.eta', {
|
||||
loggedIn: true,
|
||||
userName: request.session.user.display_name,
|
||||
|
@ -323,11 +545,20 @@ app.get("/", async (request, response) => {
|
|||
validStates: validStates,
|
||||
isStreamer: streamerInfo['userid'] == request.session.user.id,
|
||||
streamerName: streamerInfo['displayname'],
|
||||
streamerProfilePicture: streamerInfo['imageurl']
|
||||
streamerProfilePicture: streamerInfo['imageurl'],
|
||||
config: streamerConfig,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
app.get("/colors.css", async (_request, response) => {
|
||||
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
|
||||
var colors = await db.query(queries.getConfig).then((result: pg.QueryResult) => result.rows[0]['colors']);
|
||||
if (typeof streamerInfo == 'undefined') return;
|
||||
response.contentType("text/css");
|
||||
response.render('colors.eta', colors);
|
||||
});
|
||||
|
||||
// Streamer Panel
|
||||
//app.get("/streamer/", async (request, response) => {
|
||||
//
|
||||
|
|
22
src/cron.ts
22
src/cron.ts
|
@ -9,6 +9,24 @@ interface CronJob {
|
|||
(streamer: twitch.StreamerUserIdTokenPair): Promise<void>
|
||||
}
|
||||
|
||||
function validateJob(job: string) {
|
||||