diff --git a/api/main_endpoints/routes/Printer.js b/api/main_endpoints/routes/Printer.js index a0621b7e8..f02f214a9 100644 --- a/api/main_endpoints/routes/Printer.js +++ b/api/main_endpoints/routes/Printer.js @@ -6,7 +6,13 @@ const logger = require('../../util/logger'); const fs = require('fs'); const path = require('path'); const { MetricsHandler, register } = require('../../util/metrics.js'); -const { cleanUpChunks, cleanUpExpiredChunks, recordPrintingFolderSize } = require('../util/Printer.js'); +const { + cleanUpChunks, + cleanUpExpiredChunks, + recordPrintingFolderSize, + modifyPagesPrinted, + getPageCount +} = require('../util/Printer.js'); const { decodeToken, @@ -84,6 +90,9 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { logger.warn('/sendPrintRequest was requested with an invalid token'); return res.sendStatus(UNAUTHORIZED); } + + const userId = decodedToken._id; + if (!PRINTING.ENABLED) { logger.warn('Printing is disabled, returning 200 and dummy print id to mock the printing server'); return res.status(OK).send({ printId: null }); @@ -99,6 +108,7 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { const { copies, sides, id } = req.body; + // read and reassemble the pdf const chunks = await fs.promises.readdir(dir); const assembledPdfFromChunks = path.join(dir, id + '.pdf'); @@ -122,6 +132,12 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { data.append('copies', copies); data.append('sides', sides); + // update user's printed pages count + const pagesPrinted = getPageCount(assembledPdfFromChunks); + if (!modifyPagesPrinted(userId, pagesPrinted)) { + return res.sendStatus(SERVER_ERROR); + } + try { // full pdf can be sent to quasar no problem const printRes = await axios.post(PRINTER_URL + '/print', data, { diff --git a/api/main_endpoints/util/Printer.js b/api/main_endpoints/util/Printer.js index 0da5e2219..2023dfa58 100644 --- a/api/main_endpoints/util/Printer.js +++ b/api/main_endpoints/util/Printer.js @@ -2,6 +2,8 @@ const fs = require('fs'); const path = require('path'); const logger = require('../../util/logger'); const { MetricsHandler } = require('../../util/metrics.js'); +const User = require('../models/User.js'); +const { PDFDocument } = require('pdf-lib'); /** * Deletes all chunks with the specified id from a directory @@ -74,4 +76,45 @@ async function recordPrintingFolderSize(dir) { MetricsHandler.currentSizeOfPrintingFolderBytes.set(sizeOfDir); } -module.exports = { cleanUpChunks, cleanUpExpiredChunks, recordPrintingFolderSize }; + +/** + * Modify the user's pagesPrinted field based on the length of their print request + * @param {string} userId id of the current user + * @param {Number} numPages the number of pages of the current print request + * @returns {boolean} Returns if the database operation was successful + */ + +function modifyPagesPrinted(userId, numPages) { + return new Promise((resolve) => { + try { + User.findByIdAndUpdate( + userId, + { + $inc: { pagesPrinted: numPages }, + }, { + new: true, + } + , (error, result) => { + if (error) { + logger.error('modifyPagesPrinted got an error querying mongodb: ', error); + return resolve(false); + } + if (!result) { + logger.info(`User ${userId} not found in the database`); + } + return resolve(!!result); + }); + } catch (err) { + logger.error('modifyPagesPrinted encountered an error querying mongodb: ', err); + return resolve(false); + } + }); +} + +async function getPageCount(file) { + const buffer = await fs.promises.readFile(file); + const pdf = await PDFDocument.load(buffer); + return pdf.getPageCount(); +} + +module.exports = { cleanUpChunks, cleanUpExpiredChunks, recordPrintingFolderSize, modifyPagesPrinted, getPageCount }; diff --git a/api/package-lock.json b/api/package-lock.json index 331aec1b9..0059579d0 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -22,6 +22,7 @@ "nodemon": "^2.0.4", "passport": "^0.4.1", "passport-jwt": "^4.0.0", + "pdf-lib": "^1.17.1", "prom-client": "^15.1.3" }, "devDependencies": { @@ -631,6 +632,24 @@ "node": ">=8.0.0" } }, + "node_modules/@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.6" + } + }, + "node_modules/@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "license": "MIT", + "dependencies": { + "pako": "^1.0.10" + } + }, "node_modules/@types/bson": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", @@ -3286,6 +3305,12 @@ "node": ">= 0.8.0" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3369,6 +3394,18 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "node_modules/pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "license": "MIT", + "dependencies": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4010,6 +4047,12 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4659,6 +4702,22 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==" }, + "@pdf-lib/standard-fonts": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", + "integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==", + "requires": { + "pako": "^1.0.6" + } + }, + "@pdf-lib/upng": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz", + "integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==", + "requires": { + "pako": "^1.0.10" + } + }, "@types/bson": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.5.tgz", @@ -6474,6 +6533,11 @@ "word-wrap": "^1.2.5" } }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -6533,6 +6597,17 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, + "pdf-lib": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz", + "integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==", + "requires": { + "@pdf-lib/standard-fonts": "^1.0.0", + "@pdf-lib/upng": "^1.0.1", + "pako": "^1.0.11", + "tslib": "^1.11.1" + } + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -6980,6 +7055,11 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/api/package.json b/api/package.json index f680fd1ad..99caa727f 100644 --- a/api/package.json +++ b/api/package.json @@ -41,6 +41,7 @@ "nodemon": "^2.0.4", "passport": "^0.4.1", "passport-jwt": "^4.0.0", + "pdf-lib": "^1.17.1", "prom-client": "^15.1.3" }, "devDependencies": { diff --git a/src/Pages/2DPrinting/2DPrinting.js b/src/Pages/2DPrinting/2DPrinting.js index ff23d3cd0..8787c7d26 100644 --- a/src/Pages/2DPrinting/2DPrinting.js +++ b/src/Pages/2DPrinting/2DPrinting.js @@ -1,11 +1,10 @@ -import React, { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef } from 'react'; import PageSelectDropdown from './PageSelectDropdown'; import { parseRange, printPage, getPagesPrinted, } from '../../APIFunctions/2DPrinting'; -import { editUser } from '../../APIFunctions/User'; import { PDFDocument } from 'pdf-lib'; import { healthCheck } from '../../APIFunctions/2DPrinting'; @@ -203,10 +202,6 @@ export default function Printing() { let status = await printPage(data, user.token); if (!status.error) { - editUser( - { ...user, pagesPrinted: pagesPrinted + pagesToBeUsedInPrintRequest }, - user.token, - ); setPrintStatus('Printing succeeded!'); setPrintStatusColor('success'); } else { diff --git a/test/api/Printer.js b/test/api/Printer.js index 58135cc4a..94c241ea8 100644 --- a/test/api/Printer.js +++ b/test/api/Printer.js @@ -25,6 +25,8 @@ const tools = require('../util/tools/tools.js'); const crypto = require('crypto'); const token = ''; const printerUtil = require('../../api/main_endpoints/util/Printer.js'); +const User = require('../../api/main_endpoints/models/User.js'); +const { findByIdAndDelete } = require('../../api/main_endpoints/models/OfficeAccessCard.js'); let app = null; let test = null; @@ -155,4 +157,26 @@ describe('Printer', () => { expect(chunksProcessed).to.equal(TOTAL_CHUNKS); }); }); + + describe('modifyPagesPrinted', () => { + it('Should return true if pages were modified correctly', async () => { + const user = await new User({ + firstName: 'nothing', + lastName: 'really', + email: 'alerts@one.sce', + password: 'vibecoding', + pagesPrinted: 1, + }).save(); + + const userId = user._id; + + const tryModifyPagesPrinted = await printerUtil.modifyPagesPrinted(userId, 3); + expect(tryModifyPagesPrinted).to.equal(true); + + const updatedUser = await User.findById(userId); + expect(updatedUser.pagesPrinted).to.equal(4); + + await User.findByIdAndDelete(userId); + }); + }); });