parent
b479cd2948
commit
68faec1ae8
|
@ -2,6 +2,6 @@ CREATE TABLE votes (
|
||||||
requestUrl varchar,
|
requestUrl varchar,
|
||||||
userId int,
|
userId int,
|
||||||
PRIMARY KEY (requestUrl,userId),
|
PRIMARY KEY (requestUrl,userId),
|
||||||
FOREIGN KEY (requestUrl) REFERENCES requests(url),
|
FOREIGN KEY (requestUrl) REFERENCES requests(url) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (userId) REFERENCES users(userId)
|
FOREIGN KEY (userId) REFERENCES users(userId) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,5 +3,5 @@ CREATE TABLE scores (
|
||||||
score int NOT NULL DEFAULT 0,
|
score int NOT NULL DEFAULT 0,
|
||||||
scoreModifier int NOT NULL DEFAULT 0,
|
scoreModifier int NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (url),
|
PRIMARY KEY (url),
|
||||||
FOREIGN KEY (url) REFERENCES requests(url)
|
FOREIGN KEY (url) REFERENCES requests(url) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
135
public/main.js
135
public/main.js
|
@ -10,26 +10,34 @@ function getRequests(count,allRequests) {
|
||||||
reqUrl += `?count=${count}`;
|
reqUrl += `?count=${count}`;
|
||||||
fetch(reqUrl)
|
fetch(reqUrl)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(requests => buildTable(requests,allRequests));
|
.then(requests => {
|
||||||
|
window.requests = requests;
|
||||||
|
console.log(requests);
|
||||||
|
buildTable();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTable(requests,allRequests) {
|
function buildTable() {
|
||||||
var requestsDivHTML = '<table><tr><th class="request-url">URL</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
|
var requestsDivHTML = '<table><tr><th class="request-url">URL</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
|
||||||
if (allRequests) requestsDivHTML += `<th class="request-state">State</td>`;
|
requestsDivHTML += '<th class="request-state">State</td>';
|
||||||
if (window.loggedIn) requestsDivHTML += `<th class="request-vote">Vote</td>`;
|
if (window.loggedIn) requestsDivHTML += '<th class="request-vote">Vote</td>';
|
||||||
|
if (window.isStreamer) requestsDivHTML += '<th class="request-update">Update</th>'
|
||||||
requestsDivHTML += "</tr>";
|
requestsDivHTML += "</tr>";
|
||||||
for (request of requests) {
|
for (request of requests) {
|
||||||
console.log(request);
|
console.log(request);
|
||||||
requestsDivHTML += `<tr><td class="request-url"><a href="${request.url}" target="_blank">${request.url}</a></td>\
|
requestsDivHTML += `<tr><td class="request-url"><a href="${request.url}" target="_blank">${request.url}</a></td>\
|
||||||
<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
|
<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
|
||||||
<td class="request-score">${request.score}</td>`;
|
<td class="request-score">${request.score}</td>`;
|
||||||
if (allRequests) requestsDivHTML += `<td class="request-state">${request.state}</td>`;
|
requestsDivHTML += `<td class="request-state">${request.state}</td>`;
|
||||||
if (window.loggedIn) {
|
if (window.loggedIn) {
|
||||||
if (request.voted) {
|
if (request.voted) {
|
||||||
requestsDivHTML += `<td class="request-vote"><button onclick="deleteVote('${request.url}')">Unvote</button></td>`;
|
requestsDivHTML += `<td class="request-vote"><button onclick="deleteVote('${request.url}')">Unvote</button></td>`;
|
||||||
} else {
|
} else {
|
||||||
requestsDivHTML += `<td class="request-vote"><button onclick="addVote('${request.url}')">Vote</button></td>`;
|
requestsDivHTML += `<td class="request-vote"><button onclick="addVote('${request.url}')">Vote</button></td>`;
|
||||||
}
|
}
|
||||||
|
if (window.isStreamer) {
|
||||||
|
requestsDivHTML += '<td class="request-update"><button onclick="openUpdateRequestModal(this.parentElement.parentElement)">Update</button></td>'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
requestsDivHTML += "</tr>";
|
requestsDivHTML += "</tr>";
|
||||||
}
|
}
|
||||||
|
@ -38,9 +46,8 @@ function buildTable(requests,allRequests) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTable() {
|
function updateTable() {
|
||||||
var count = document.getElementById("count").value;
|
allRequests = document.getElementById("allRequests").checked;
|
||||||
var allRequests = document.getElementById("allRequests").checked;
|
getRequests(document.getElementById("count").value,allRequests);
|
||||||
getRequests(count,allRequests);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRequestErr(msg) {
|
function addRequestErr(msg) {
|
||||||
|
@ -53,8 +60,21 @@ function addRequestErrReset() {
|
||||||
document.getElementById('addRequestError').innerText = "";
|
document.getElementById('addRequestError').innerText = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateRequestErr(msg) {
|
||||||
|
document.getElementById('updateRequestError').style.display = "inline-block";
|
||||||
|
document.getElementById('updateRequestError').innerText = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestErrReset() {
|
||||||
|
document.getElementById('updateRequestError').style.display = "none";
|
||||||
|
document.getElementById('updateRequestError').innerText = "";
|
||||||
|
}
|
||||||
|
|
||||||
function showMessage(msg) {
|
function showMessage(msg) {
|
||||||
document.getElementById("messageModalText").innerText = msg;
|
document.getElementById("messageModalText").innerText = msg;
|
||||||
|
document.getElementById("addRequestModal").style.display = "none";
|
||||||
|
document.getElementById("updateRequestModal").style.display = "none";
|
||||||
|
document.getElementById("deleteRequestModal").style.display = "none";
|
||||||
document.getElementById("modalBackground").style.display = "flex";
|
document.getElementById("modalBackground").style.display = "flex";
|
||||||
document.getElementById("messageModal").style.display = "block";
|
document.getElementById("messageModal").style.display = "block";
|
||||||
}
|
}
|
||||||
|
@ -66,6 +86,8 @@ function closeMessageModal() {
|
||||||
|
|
||||||
function openAddRequestModal() {
|
function openAddRequestModal() {
|
||||||
document.getElementById("modalBackground").style.display = "flex";
|
document.getElementById("modalBackground").style.display = "flex";
|
||||||
|
document.getElementById("updateRequestModal").style.display = "none";
|
||||||
|
document.getElementById("messageModal").style.display = "none";
|
||||||
document.getElementById("addRequestModal").style.display = "block";
|
document.getElementById("addRequestModal").style.display = "block";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,6 +96,49 @@ function closeAddRequestModal() {
|
||||||
document.getElementById("addRequestModal").style.display = "none";
|
document.getElementById("addRequestModal").style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openUpdateRequestModal(tr) {
|
||||||
|
var url = tr.getElementsByClassName('request-url')[0].innerText;
|
||||||
|
var score = tr.getElementsByClassName('request-score')[0].innerText;
|
||||||
|
var state = tr.getElementsByClassName('request-state')[0].innerText;
|
||||||
|
document.getElementById("updateRequestUrl").href = url;
|
||||||
|
document.getElementById("updateRequestUrl").innerText = url;
|
||||||
|
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) {
|
||||||
|
document.getElementById("updateRequestUrl").href = url;
|
||||||
|
document.getElementById("updateRequestUrl").innerText = url;
|
||||||
|
document.getElementById("messageModal").style.display = "none";
|
||||||
|
document.getElementById("addRequestModal").style.display = "none";
|
||||||
|
document.getElementById("updateRequestModal").style.display = "none";
|
||||||
|
document.getElementById("modalBackground").style.display = "flex";
|
||||||
|
document.getElementById("deleteRequestModal").style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDeleteRequestModal() {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
|
||||||
const validUrlRegexes = [
|
const validUrlRegexes = [
|
||||||
/^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/
|
/^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/
|
||||||
];
|
];
|
||||||
|
@ -132,6 +197,50 @@ function deleteVote(url) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateRequestState(url,state) {
|
||||||
|
updateRequestErrReset();
|
||||||
|
fetch("/api/updateRequestState", { method: 'POST', body: new URLSearchParams({
|
||||||
|
url: url,
|
||||||
|
state: state
|
||||||
|
})})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
response.text().then(updateRequestErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateTable();
|
||||||
|
response.text().then(showMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRequestScoreModifier(url,scoreDiff) {
|
||||||
|
updateRequestErrReset();
|
||||||
|
fetch("/api/updateRequestScoreModifier", { method: 'POST', body: new URLSearchParams({
|
||||||
|
url: url,
|
||||||
|
scoreDiff: scoreDiff
|
||||||
|
})})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
response.text().then(updateRequestErr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateTable();
|
||||||
|
response.text().then(showMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteRequest(url) {
|
||||||
|
fetch("/api/deleteRequest", { method: 'POST', body: new URLSearchParams({
|
||||||
|
url: url
|
||||||
|
})})
|
||||||
|
.then(response => {
|
||||||
|
updateTable();
|
||||||
|
response.text().then(showMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
updateTable();
|
updateTable();
|
||||||
|
|
||||||
document.addEventListener("keydown", function onEvent(event) {
|
document.addEventListener("keydown", function onEvent(event) {
|
||||||
|
@ -140,4 +249,12 @@ document.addEventListener("keydown", function onEvent(event) {
|
||||||
closeAddRequestModal();
|
closeAddRequestModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
document.getElementById("modalBackground").addEventListener("click", (e) => { if (e.target === e.currentTarget) closeAddRequestModal();});
|
document.getElementById("modalBackground").addEventListener("click", (e) => { if (e.target === e.currentTarget) closeAllModals();});
|
||||||
|
|
||||||
|
var updateRequestStateSelect = document.getElementById("updateRequestStateSelect");
|
||||||
|
for(state of validStates) {
|
||||||
|
var opt = document.createElement("option");
|
||||||
|
opt.text = state;
|
||||||
|
opt.value = state;
|
||||||
|
updateRequestStateSelect.add(opt)
|
||||||
|
}
|
||||||
|
|
|
@ -130,7 +130,7 @@ div#nav-userpic {
|
||||||
box-shadow: 0px 5px 20px black;
|
box-shadow: 0px 5px 20px black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal h1 {
|
.modal h1, .modal h2, .modal h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,3 +166,16 @@ div#nav-userpic {
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
margin-bottom: -7px;
|
margin-bottom: -7px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#scoreModifierInput {
|
||||||
|
width: 4em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#scoreModifierHelp {
|
||||||
|
font-size: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#deleteRequestLink {
|
||||||
|
color: #f00;
|
||||||
|
}
|
||||||
|
|
|
@ -274,6 +274,7 @@ app.get("/callback", async (request, response) => {
|
||||||
app.get("/", async (request, response) => {
|
app.get("/", async (request, response) => {
|
||||||
if (request.session) await validateApiToken(request.session);
|
if (request.session) await validateApiToken(request.session);
|
||||||
var streamerInfo = await db.query(queries.getStreamerInfo).then((result: pg.QueryResult) => result.rows[0]);
|
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));
|
||||||
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`);
|
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`);
|
||||||
if (!request.session || !request.session.user) {
|
if (!request.session || !request.session.user) {
|
||||||
response.render('main.eta', {
|
response.render('main.eta', {
|
||||||
|
@ -288,6 +289,8 @@ app.get("/", async (request, response) => {
|
||||||
loggedIn: true,
|
loggedIn: true,
|
||||||
userName: request.session.user.display_name,
|
userName: request.session.user.display_name,
|
||||||
userProfilePicture: request.session.user.profile_image_url,
|
userProfilePicture: request.session.user.profile_image_url,
|
||||||
|
validStates: validStates,
|
||||||
|
isStreamer: streamerInfo['userid'] == request.session.user.id,
|
||||||
streamerName: streamerInfo['displayname'],
|
streamerName: streamerInfo['displayname'],
|
||||||
streamerProfilePicture: streamerInfo['imageurl']
|
streamerProfilePicture: streamerInfo['imageurl']
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ export const getStreamerIdToken = {
|
||||||
|
|
||||||
export const getStreamerInfo = {
|
export const getStreamerInfo = {
|
||||||
name: "getStreamerInfo",
|
name: "getStreamerInfo",
|
||||||
text: "SELECT displayname,imageurl FROM streamer_user_vw"
|
text: "SELECT userid,displayname,imageurl FROM streamer_user_vw"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateStreamer = {
|
export const updateStreamer = {
|
||||||
|
@ -91,3 +91,8 @@ export const checkVoteExists = {
|
||||||
name: "checkVoteExists",
|
name: "checkVoteExists",
|
||||||
text: "SELECT userid FROM votes WHERE requesturl = $1 AND userid = $2"
|
text: "SELECT userid FROM votes WHERE requesturl = $1 AND userid = $2"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getValidStates = {
|
||||||
|
name: "getValidStates",
|
||||||
|
text: "SELECT * FROM states"
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@
|
||||||
<head>
|
<head>
|
||||||
<link rel=stylesheet href=style.css />
|
<link rel=stylesheet href=style.css />
|
||||||
<title><%= it.streamerName %>'s Learn Request Queue</title>
|
<title><%= it.streamerName %>'s Learn Request Queue</title>
|
||||||
<script>window.loggedIn = <%= it.loggedIn %></script>
|
<script>
|
||||||
|
window.loggedIn = <%= it.loggedIn %>;
|
||||||
|
window.validStates = <%~ it.validStates %>;
|
||||||
|
window.isStreamer = <%= it.isStreamer %>;
|
||||||
|
</script>
|
||||||
<script src="main.js" defer></script>
|
<script src="main.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -32,7 +36,7 @@
|
||||||
</select>
|
</select>
|
||||||
<input type="checkbox" id="allRequests" onchange="updateTable()">View requests in any state</input>
|
<input type="checkbox" id="allRequests" onchange="updateTable()">View requests in any state</input>
|
||||||
</div>
|
</div>
|
||||||
<div id="modalBackground">
|
<div id="modalBackground">
|
||||||
<div class="modal" id="messageModal">
|
<div class="modal" id="messageModal">
|
||||||
<div class="modalClose"><a href="#" onclick="closeMessageModal()">×</a></div>
|
<div class="modalClose"><a href="#" onclick="closeMessageModal()">×</a></div>
|
||||||
<span id="messageModalText"></span>
|
<span id="messageModalText"></span>
|
||||||
|
@ -47,6 +51,55 @@
|
||||||
</span>
|
</span>
|
||||||
Currently, only Youtube links are accepted.
|
Currently, only Youtube links are accepted.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal" id="updateRequestModal">
|
||||||
|
<div class="modalClose"><a href="#" onclick="closeAddRequestModal()">×</a></div>
|
||||||
|
<h2>Update Request</h2>
|
||||||
|
<div class="error" id="updateRequestError"></div>
|
||||||
|
<br>
|
||||||
|
<a id="updateRequestUrl"></a>
|
||||||
|
<br>
|
||||||
|
Current Score: <span id="updateRequestModalCurrentScore"></span>
|
||||||
|
<hr>
|
||||||
|
<div>
|
||||||
|
State:
|
||||||
|
<select id="updateRequestStateSelect" onchange="updateRequestState(
|
||||||
|
document.getElementById('updateRequestUrl').innerText,
|
||||||
|
this.value)">Submit</button>
|
||||||
|
)"></select>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
Modify Score:
|
||||||
|
<input type="number" id="scoreModifierInput"></input>
|
||||||
|
<button onclick="updateRequestScoreModifier(
|
||||||
|
document.getElementById('updateRequestUrl').innerText,
|
||||||
|
document.getElementById('scoreModifierInput').value)">Submit</button>
|
||||||
|
<br>
|
||||||
|
<span id="scoreModifierHelp">
|
||||||
|
Enter a number to add to (or negative to subtract from) the score of
|
||||||
|
a request. Use this for things like donations and channel points
|
||||||
|
redemptions.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div>
|
||||||
|
<a id="deleteRequestLink" href="#" onclick="openDeleteRequestModal(
|
||||||
|
document.getElementById('updateRequestUrl').innerText
|
||||||
|
)">Delete Request</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" id="deleteRequestModal">
|
||||||
|
<div class="modalClose"><a href="#" onclick="closeDeleteRequestModal()">×</a></div>
|
||||||
|
<h2>Delete Request</h2>
|
||||||
|
<span style="color: #f00">WARNING:</span>
|
||||||
|
Deleting a request will remove the request and all votes for it from the
|
||||||
|
database. This action is irreversible. It will NOT prevent the request
|
||||||
|
from being made again - use the Rejected state for that. Are you sure
|
||||||
|
you want to delete this request?
|
||||||
|
<br><br>
|
||||||
|
<button onclick="closeDeleteRequestModal()">No</button>
|
||||||
|
<button onclick="deleteRequest(document.getElementById('updateRequestUrl').innerText)">Yes</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue