diff --git a/api/main_endpoints/models/User.js b/api/main_endpoints/models/User.js index 35a3450be..96bc45da3 100644 --- a/api/main_endpoints/models/User.js +++ b/api/main_endpoints/models/User.js @@ -70,6 +70,10 @@ const UserSchema = new Schema( type: Number, default: 0 }, + escrowPagesPrinted: { + type: Number, + default: 0 + }, apiKey: { type: String, default: '' diff --git a/api/main_endpoints/routes/Printer.js b/api/main_endpoints/routes/Printer.js index a0621b7e8..909aa8a3d 100644 --- a/api/main_endpoints/routes/Printer.js +++ b/api/main_endpoints/routes/Printer.js @@ -17,10 +17,12 @@ const { UNAUTHORIZED, NOT_FOUND, SERVER_ERROR, + BAD_REQUEST } = require('../../util/constants').STATUS_CODES; const { PRINTING = {} } = require('../../config/config.json'); +const User = require('../models/User.js'); // see https://github.com/SCE-Development/Quasar/tree/dev/docker-compose.dev.yml#L11 let PRINTER_URL = process.env.PRINTER_URL @@ -89,6 +91,7 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { return res.status(OK).send({ printId: null }); } + const user = await User.findById(decodedToken._id); const dir = path.join(__dirname, 'printing'); const { totalChunks, chunkIdx } = req.body; @@ -97,7 +100,7 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { return res.sendStatus(OK); } - const { copies, sides, id } = req.body; + const { copies, sides, id, totalPages } = req.body; const chunks = await fs.promises.readdir(dir); const assembledPdfFromChunks = path.join(dir, id + '.pdf'); @@ -116,12 +119,17 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { } } - const stream = await fs.createReadStream(assembledPdfFromChunks); + const stream = await fs.promises.readFile(assembledPdfFromChunks); const data = new FormData(); data.append('file', stream, {filename: id, type: 'application/pdf'}); data.append('copies', copies); data.append('sides', sides); + if (Number(totalPages) > 30 - user.pagesPrinted - user.escrowPagesPrinted) { + await cleanUpChunks(dir, id); + return res.sendStatus(BAD_REQUEST); + } + try { // full pdf can be sent to quasar no problem const printRes = await axios.post(PRINTER_URL + '/print', data, { @@ -137,6 +145,9 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { await cleanUpChunks(dir, id); res.status(OK).send(printId); + + user.escrowPagesPrinted += Number(totalPages); + await user.save(); } catch (err) { logger.error('/sendPrintRequest had an error: ', err); @@ -145,4 +156,50 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { } }); +router.get('/status', async (req, res) => { + if (!checkIfTokenSent(req)) { + logger.warn('/status was requested without a token'); + return res.sendStatus(UNAUTHORIZED); + } + + const decodedToken = await decodeToken(req); + if (!decodedToken || Object.keys(decodedToken) === 0) { + logger.warn('/status was requested with an invalid token'); + return res.sendStatus(UNAUTHORIZED); + } + if (!PRINTING.ENABLED) { + logger.warn('Printing is disabled, returning 200 and completed status to mock the printing server'); + return res.status(OK).send({ status: 'completed' }); + } + + try { + const url = new URL('/status/', PRINTER_URL); + url.searchParams.append('id', req.query.id); + const response = await fetch(url, { + method: 'GET', + }); + + const user = await User.findById(decodedToken._id); + + // { status: string } + const json = await response.json(); + const pages = Math.abs(Number(req.query.pages)); + + if (json.status === 'completed') { + user.pagesPrinted += pages; + user.escrowPagesPrinted -= pages; + await user.save(); + } + + if (json.status === 'failed') { + user.escrowPagesPrinted -= pages; + await user.save(); + } + + res.status(OK).send(json); + } catch (err) { + res.sendStatus(SERVER_ERROR); + } +}); + module.exports = router; diff --git a/api/main_endpoints/routes/User.js b/api/main_endpoints/routes/User.js index a9c0d37b5..4f0e72704 100644 --- a/api/main_endpoints/routes/User.js +++ b/api/main_endpoints/routes/User.js @@ -112,6 +112,7 @@ router.post('/search', function(req, res) { lastLogin: result.lastLogin, membershipValidUntil: result.membershipValidUntil, pagesPrinted: result.pagesPrinted, + escrowPagesPrinted: result.escrowPagesPrinted, doorCode: result.doorCode, _id: result._id }; @@ -312,7 +313,7 @@ router.post('/getPagesPrintedCount', (req, res) => { .status(NOT_FOUND) .send({ message: `${req.body.email} not found.` }); } - return res.status(OK).json(result.pagesPrinted); + return res.status(OK).json(result.pagesPrinted + result.escrowPagesPrinted); }); }); diff --git a/src/APIFunctions/2DPrinting.js b/src/APIFunctions/2DPrinting.js index 9c1ea06a0..51ad4b8b3 100644 --- a/src/APIFunctions/2DPrinting.js +++ b/src/APIFunctions/2DPrinting.js @@ -60,6 +60,24 @@ export function parseRange(pages, maxPages) { return result; } +export async function getPrintStatus(printId, totalPages, token) { + const url = new URL('/api/Printer/status', BASE_API_URL); + url.searchParams.append('id', printId); + url.searchParams.append('pages', totalPages); + + const response = await fetch(url, { + headers: { + 'Authorization': `Bearer ${token}` + }, + method: 'GET', + }); + + const json = await response.json(); + const status = json.status; + + return status; +} + /** * Print the page * @param {Object} data - PDF File and its configurations @@ -80,6 +98,7 @@ export async function printPage(data, token) { const pdf = data.get('file'); const sides = data.get('sides'); const copies = data.get('copies'); + const totalPages = data.get('totalPages'); const id = crypto.randomUUID(); const CHUNK_SIZE = 1024 * 1024 * 0.5; // 0.5 MB ------- SENT DATA **CANNOT** EXCEED 1 MB const totalChunks = Math.ceil(pdf.size / CHUNK_SIZE); @@ -98,6 +117,7 @@ export async function printPage(data, token) { chunkData.append('id', id); chunkData.append('sides', sides); chunkData.append('copies', copies); + chunkData.append('totalPages', totalPages); } try { diff --git a/src/APIFunctions/User.js b/src/APIFunctions/User.js index cb072a556..bcb9ebbd1 100644 --- a/src/APIFunctions/User.js +++ b/src/APIFunctions/User.js @@ -96,6 +96,7 @@ export async function editUser(userToEdit, token) { discordDiscrim, discordID, pagesPrinted, + escrowPagesPrinted, accessLevel, lastLogin, emailVerified, @@ -122,6 +123,7 @@ export async function editUser(userToEdit, token) { discordDiscrim, discordID, pagesPrinted, + escrowPagesPrinted, accessLevel, lastLogin, emailVerified, diff --git a/src/Components/Printing/JobStatus.js b/src/Components/Printing/JobStatus.js new file mode 100644 index 000000000..55f01a581 --- /dev/null +++ b/src/Components/Printing/JobStatus.js @@ -0,0 +1,14 @@ +import React from 'react'; + +export default function JobStatus(props) { + return ( +
{props.fileName} ({props.id}): {props.status}
+