diff --git a/api/main_endpoints/routes/Printer.js b/api/main_endpoints/routes/Printer.js index 73a5c97f7..0ecb1b574 100644 --- a/api/main_endpoints/routes/Printer.js +++ b/api/main_endpoints/routes/Printer.js @@ -21,10 +21,11 @@ const { const { PRINTING = {} } = require('../../config/config.json'); +const AuditLogActions = require('../util/auditLogActions.js'); +const AuditLog = require('../models/AuditLog.js'); // see https://github.com/SCE-Development/Quasar/tree/dev/docker-compose.dev.yml#L11 -let PRINTER_URL = process.env.PRINTER_URL - || 'http://localhost:14000'; +let PRINTER_URL = process.env.PRINTER_URL || 'http://localhost:14000'; const router = express.Router(); @@ -36,7 +37,7 @@ const storage = multer.diskStorage({ filename: function(req, file, cb) { const uniqueSuffix = Date.now(); cb(null, uniqueSuffix + '_' + file.originalname); - } + }, }); const upload = multer({ storage }); @@ -58,7 +59,9 @@ router.get('/healthCheck', async (req, res) => { * https://github.com/SCE-Development/Quasar/wiki/How-do-Health-Checks-Work%3F */ if (!PRINTING.ENABLED) { - logger.warn('Printing is disabled, returning 200 to mock the printing server'); + logger.warn( + 'Printing is disabled, returning 200 to mock the printing server' + ); return res.sendStatus(OK); } await axios @@ -74,24 +77,19 @@ router.get('/healthCheck', async (req, res) => { }); router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { + let totalFileSize = 0; + if (!checkIfTokenSent(req)) { logger.warn('/sendPrintRequest was requested without a token'); return res.sendStatus(UNAUTHORIZED); } - if (!await decodeToken(req)) { + const user = decodeToken(req); + if (!user) { logger.warn('/sendPrintRequest was requested with an invalid token'); return res.sendStatus(UNAUTHORIZED); } 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 }); - } - - const dir = path.join(__dirname, 'printing'); - const { totalChunks, chunkIdx } = req.body; - - // reassemble pdf on last chunk received - if (Number(chunkIdx) < totalChunks - 1) { + logger.warn('Printing is disabled, returning 200 to mock the printing server'); return res.sendStatus(OK); } @@ -106,6 +104,7 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { try { const chunkData = await fs.promises.readFile(path.join(dir, chunk)); + totalFileSize += chunkData.length; fs.appendFileSync(assembledPdfFromChunks, chunkData); } catch (err) { logger.error('/sendPrintRequest encountered an error while assembling pdf: ' + err); @@ -116,31 +115,43 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => { const stream = await fs.createReadStream(assembledPdfFromChunks); const data = new FormData(); - data.append('file', stream, {filename: id, type: 'application/pdf'}); + data.append('file', fs.createReadStream(file.path), { filename: file.originalname }); data.append('copies', copies); data.append('sides', sides); - - try { - // full pdf can be sent to quasar no problem - const printRes = await axios.post(PRINTER_URL + '/print', data, { + axios.post(PRINTER_URL + '/print', + data, + { headers: { ...data.getHeaders(), - }, - maxContentLength: 1024 * 1024 * 150, // 150 mb - maxBodyLength: Infinity + } + }) + .then( async () => { + + // create audit log on print + await AuditLog.create({ + userId: user._id, + action: AuditLogActions.PRINT_PAGE, + details: { + copies: parseInt(copies), + sides, + fileSize: totalFileSize, + userEmail: user.email, + printedAt: new Date(), + printJobId: id + } + }).catch(logger.error); + + // delete file from temp folder after printing + fs.unlink(file.path, (err) => { + if (err) { + logger.error(`Unable to delete file at path ${file.path}:`, err); + } + }); + res.sendStatus(OK); + }).catch((err) => { + logger.error('/sendPrintRequest had an error: ', err); + res.sendStatus(SERVER_ERROR); }); - - // { print_id: null | string } - const printId = printRes.data; - - await cleanUpChunks(dir, id); - res.status(OK).send(printId); - } catch (err) { - logger.error('/sendPrintRequest had an error: ', err); - - await cleanUpChunks(dir, id); - res.sendStatus(SERVER_ERROR); - } }); module.exports = router; diff --git a/test/api/Printer.js b/test/api/Printer.js index 58135cc4a..1b04de0b1 100644 --- a/test/api/Printer.js +++ b/test/api/Printer.js @@ -1,8 +1,10 @@ process.env.NODE_ENV = 'test'; - +const { PRINTING = {} } = require('../../api/config/config.json'); +const mongoose = require('mongoose'); const chai = require('chai'); const chaiHttp = require('chai-http'); const fs = require('fs'); +const User = require('../../api/main_endpoints/models/User.js'); const { OK, @@ -25,6 +27,10 @@ const tools = require('../util/tools/tools.js'); const crypto = require('crypto'); const token = ''; const printerUtil = require('../../api/main_endpoints/util/Printer.js'); +const { MEMBERSHIP_STATE } = require('../../api/util/constants'); + +const AuditLogActions = require('../../api/main_endpoints/util/auditLogActions.js'); +const AuditLog = require('../../api/main_endpoints/models/AuditLog.js'); let app = null; let test = null; @@ -41,6 +47,7 @@ describe('Printer', () => { __dirname + '/../../api/main_endpoints/routes/Printer.js', ]); test = new SceApiTester(app); + done(); }); @@ -50,14 +57,19 @@ describe('Printer', () => { tools.terminateServer(done); }); - beforeEach(() => { + beforeEach(async () => { + await User.deleteMany({}); + await AuditLog.deleteMany({}); setTokenStatus(false); }); - afterEach(() => { + afterEach(async () => { + await User.deleteMany({}); + await AuditLog.deleteMany({}); resetTokenMock(); }); + const url = '/api/Printer/sendPrintRequest'; describe('cleanUpExpiredChunks', () => { const CHUNK_DIRECTORY = __dirname + '/../../api/main_endpoints/routes/printing'; const MY_BIRTH_DATE = new Date('December 4, 2005 07:53:00'); @@ -126,6 +138,7 @@ describe('Printer', () => { expect(result).to.have.status(UNAUTHORIZED); }); + it(`Should successfully process all ${TOTAL_CHUNKS} chunks sent (with valid token)`, async () => { let chunksProcessed = 0; setTokenStatus(true); @@ -154,5 +167,39 @@ describe('Printer', () => { expect(chunksProcessed).to.equal(TOTAL_CHUNKS); }); + + it('Should create only one audit log in the database when the response status is 200', async () => { + // Skip tests if printing is disabled + if (!PRINTING.ENABLED) { + return; + } + + const userId = new mongoose.Types.ObjectId(); + const user = new User({ + _id: userId, + firstName: 'first_name', + lastName: 'last_name', + email: 'print_user@b.c', + password: 'Passw0rd123', + emailVerified: true, + accessLevel: MEMBERSHIP_STATE.MEMBER, + }); + await user.save(); + + setTokenStatus(true, { + _id: user._id, + email: user.email, + accessLevel: user.accessLevel, + }); + + const result = await test.sendPostRequestWithToken(token, url, { DUMMY_CHUNK }); + const auditEntry = await AuditLog.findOne({ + action: AuditLogActions.PRINT_PAGE, + }).lean(); + + expect(result).to.have.status(OK); + expect(auditEntry).to.exist; + expect(await AuditLog.countDocuments()).to.equal(1); + }); }); }); diff --git a/test/api/ShortcutSearch.js b/test/api/ShortcutSearch.js index faa61dcde..b5544d9bf 100644 --- a/test/api/ShortcutSearch.js +++ b/test/api/ShortcutSearch.js @@ -263,8 +263,8 @@ describe('ShortcutSearch', () => { }); }); - after(() => { - User.deleteMany({}); + after(async () => { + await User.deleteMany({}); }); }); }); diff --git a/test/api/User.js b/test/api/User.js index cd97047cb..0a5b1a9c3 100644 --- a/test/api/User.js +++ b/test/api/User.js @@ -835,9 +835,10 @@ describe('User', () => { expect(result.body.newAnnualMembers).to.equal(2); }); - after(() => { + after(async () => { revertClock(); - User.deleteMany({}); + await User.deleteMany({}); + await AuditLog.deleteMany({}); }); }); });