From 84a22ccffd055e68ce04ec63d2972353e87da937 Mon Sep 17 00:00:00 2001 From: Dessa Simpson Date: Thu, 25 Feb 2021 19:52:57 -0700 Subject: [PATCH] Add pagination Note: Requests API endpoints /getRequests and /getRequestsAll have breaking changes (the response schema changed entirely). Fixes #26 --- public/main.js | 57 +++++++++++++++++++++++++++++++++++------------- public/style.css | 5 +++++ src/app.ts | 43 +++++++++++++++++++++++++----------- src/queries.ts | 27 ++++++++++++++++------- src/requests.ts | 26 +++++++++++++++------- views/main.eta | 34 +++++++++++++++++++++-------- 6 files changed, 139 insertions(+), 53 deletions(-) diff --git a/public/main.js b/public/main.js index 73559b2..3b2da71 100644 --- a/public/main.js +++ b/public/main.js @@ -1,29 +1,49 @@ var requestsDiv = document.getElementById("requests"); -var cronJobs = ['processBans'] +var cronJobs = ['processBans']; +var currentPage = 1; +var totalPages = 1; -function getRequests(count,allRequests) { - var reqUrl; - if (allRequests) { - reqUrl = "/api/getAllRequests"; - } else { - reqUrl = "/api/getRequests"; - } - reqUrl += `?count=${count}`; +function getRequests(count,offset,allRequests) { + if (allRequests) var reqUrl = "/api/getAllRequests"; + else var reqUrl = "/api/getRequests"; + reqUrl += `?count=${count}&offset=${offset}`; fetch(reqUrl) .then(response => response.json()) .then(requests => { - window.requests = requests; - buildTable(); + buildTable(requests); }); } -function buildTable() { +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 += ``; + } + document.getElementById("page").value = currentPage; var requestsDivHTML = ''; requestsDivHTML += '' requestsDivHTML += ""; - for (request of requests) { + for (request of requests.requests) { requestsDivHTML += `\ \ `; @@ -45,8 +65,10 @@ function buildTable() { } function updateTable() { - allRequests = document.getElementById("allRequests").checked; - getRequests(document.getElementById("count").value,allRequests); + var count = document.getElementById("count").value; + var offset = (currentPage - 1) * count; + var allRequests = document.getElementById("allRequests").checked; + getRequests(count,offset,allRequests); } function applyUrlTransforms(url) { @@ -66,6 +88,11 @@ function applyUrlTransforms(url) { } } +function goToPage(page) { + currentPage = parseInt(page,10); + updateTable(); +} + function getColorObject() { return { bg: { diff --git a/public/style.css b/public/style.css index 5ff0029..6be863d 100644 --- a/public/style.css +++ b/public/style.css @@ -111,6 +111,11 @@ div#nav-userpic { text-align: right; } +#tableSettings { + display: flex; + justify-content: space-between; +} + #modalBackground { display: none; position: fixed; diff --git a/src/app.ts b/src/app.ts index 3e931ce..efaa358 100644 --- a/src/app.ts +++ b/src/app.ts @@ -42,31 +42,47 @@ 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 requestsTotal = await requests.getRequestsTotal(); if (request.session.user) { - requests.getRequestsVoted(requestCount,request.session.user.id).then((val: Array) => response.send(val)) + requests.getRequestsVoted(requestCount,requestOffset,request.session.user.id) + .then((val: Array) => response.send({ + total: requestsTotal, + requests: val + })) .catch((e: any) => errorHandler(request,response,e)); } else { - requests.getRequests(requestCount).then((val: Array) => response.send(val)) + requests.getRequests(requestCount,requestOffset) + .then((val: Array) => 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 requestsTotal = await requests.getAllRequestsTotal(); if (request.session.user) { - requests.getAllRequestsVoted(requestCount,request.session.user.id).then((val: Array) => response.send(val)) + requests.getAllRequestsVoted(requestCount,requestOffset,request.session.user.id) + .then((val: Array) => response.send({ + total: requestsTotal, + requests: val + })) .catch((e: any) => errorHandler(request,response,e)); } else { - requests.getAllRequests(requestCount).then((val: Array) => response.send(val)) + requests.getAllRequests(requestCount,requestOffset) + .then((val: Array) => response.send({ + total: requestsTotal, + requests: val + })) .catch((e: any) => errorHandler(request,response,e)); } }); @@ -79,7 +95,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."); diff --git a/src/queries.ts b/src/queries.ts index 9b1712d..b30e482 100644 --- a/src/queries.ts +++ b/src/queries.ts @@ -56,27 +56,38 @@ export const updateVotePoints = { export const getRequests = { name: "getRequests", text: "SELECT * FROM requests_vw \ - JOIN states ON requests_vw.state = states.state \ - WHERE active ORDER BY score DESC, reqTimestamp ASC LIMIT $1" + JOIN states ON requests_vw.state = states.state WHERE active \ + ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2" } export const getRequestsVoted = { name: "getRequestsVoted", - text: "SELECT * FROM get_requests_voted($2) \ - JOIN states ON get_requests_voted.state = states.state \ - WHERE active ORDER BY score DESC, reqTimestamp ASC LIMIT $1" + text: "SELECT * FROM get_requests_voted($3) \ + JOIN states ON get_requests_voted.state = states.state WHERE active \ + ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2" +} + +export const getRequestsTotal = { + name: "getRequestsTotal", + text: "SELECT COUNT(*) FROM requests_vw \ + JOIN states ON requests_vw.state = states.state WHERE active" } export const getAllRequests = { name: "getAllRequests", text: "SELECT * FROM requests_vw \ - ORDER BY score DESC, reqTimestamp ASC LIMIT $1" + ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2" } export const getAllRequestsVoted = { name: "getAllRequestsVoted", - text: "SELECT * FROM get_requests_voted($2) \ - ORDER BY score DESC, reqTimestamp ASC LIMIT $1" + text: "SELECT * FROM get_requests_voted($3) \ + ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2" +} + +export const getAllRequestsTotal = { + name: "getAllRequestsTotal", + text: "SELECT COUNT(*) FROM requests_vw" } export const checkRequestExists = { diff --git a/src/requests.ts b/src/requests.ts index d23611e..ef3e1e4 100644 --- a/src/requests.ts +++ b/src/requests.ts @@ -4,30 +4,40 @@ import { log, LogLevel } from "./logging" import pg from "pg"; import db from "./db"; -export async function getRequests(count: number) { - var query = Object.assign(queries.getRequests, { values: [count] }); +export async function getRequests(count: number, offset: number) { + var query = Object.assign(queries.getRequests, { values: [count,offset] }); return db.query(query) .then((result: pg.QueryResult) => result.rows); }; -export async function getAllRequests(count: number) { - var query = Object.assign(queries.getAllRequests, { values: [count] }); +export async function getRequestsVoted(count: number, offset: number, user: number) { + var query = Object.assign(queries.getRequestsVoted, { values: [count,offset,user] }); return db.query(query) .then((result: pg.QueryResult) => result.rows); }; -export async function getRequestsVoted(count: number, user: number) { - var query = Object.assign(queries.getRequestsVoted, { values: [count,user] }); +export async function getRequestsTotal() { + return db.query(queries.getRequestsTotal) + .then((result: pg.QueryResult) => result.rows[0]["count"]); +}; + +export async function getAllRequests(count: number, offset: number) { + var query = Object.assign(queries.getAllRequests, { values: [count,offset] }); return db.query(query) .then((result: pg.QueryResult) => result.rows); }; -export async function getAllRequestsVoted(count: number,user: number) { - var query = Object.assign(queries.getAllRequestsVoted, { values: [count,user] }); +export async function getAllRequestsVoted(count: number, offset: number, user: number) { + var query = Object.assign(queries.getAllRequestsVoted, { values: [count,offset,user] }); return db.query(query) .then((result: pg.QueryResult) => result.rows); }; +export async function getAllRequestsTotal() { + return db.query(queries.getAllRequestsTotal) + .then((result: pg.QueryResult) => result.rows[0]["count"]); +}; + const validUrlRegexes = [ /^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/ ]; diff --git a/views/main.eta b/views/main.eta index f17520c..9f986d1 100644 --- a/views/main.eta +++ b/views/main.eta @@ -30,15 +30,31 @@

- Count: - - View requests in any state +
+ + Count: + + + + + + + of ? + + + + + View learned and rejected requests + +
SongRequesterScoreState'; if (window.loggedIn) requestsDivHTML += 'Vote'; if (window.isStreamer) requestsDivHTML += 'Update
${request.imageurl ? `` : ''}${request.requester}${request.score}