From 4c2a07853092bfdf5555aba5159ea228ca834647 Mon Sep 17 00:00:00 2001 From: matt Date: Wed, 23 Mar 2022 23:29:55 +0000 Subject: [PATCH] Refactored database connection system to use a shared pool per-request --- app.js | 11 +++++++-- lib/Account.js | 17 +++++++------- lib/Class.js | 38 ++++++++++++++---------------- lib/Parent.js | 7 +++--- lib/PasswordReset.js | 24 ++++++++----------- lib/Student.js | 7 +++--- lib/Subject.js | 19 +++++++-------- lib/Test.js | 21 +++++++++-------- lib/TestTemplate.js | 37 +++++++++++------------------ lib/User.js | 55 +++++++++++++++++++++++++------------------- routes/admin.js | 12 ++++++---- routes/class.js | 19 ++++++++------- routes/main.js | 14 ++++++++--- routes/test.js | 25 +++++++++++++------- 14 files changed, 162 insertions(+), 144 deletions(-) diff --git a/app.js b/app.js index 7ad294f..4eae037 100644 --- a/app.js +++ b/app.js @@ -41,14 +41,21 @@ async function main() { const serverOptions = importJSON('server'); const sessionOptions = importJSON('session'); + const dbp = await new DatabaseConnectionPool(); + // Set up express-session to store in mysql database const mysqlStore = require('express-mysql-session')(session); - const sessionStore = - new mysqlStore({}, (await new DatabaseConnectionPool()).pool); + const sessionStore = new mysqlStore({}, dbp.pool); // Initialise express app const app = express(); + // Set up global database pool + app.use((req, res, next) => { + req.db = dbp; + next(); + }); + // Set up templating language and path app.engine( 'hbs', diff --git a/lib/Account.js b/lib/Account.js index a0f6086..90c16e8 100644 --- a/lib/Account.js +++ b/lib/Account.js @@ -1,15 +1,13 @@ /* eslint-disable no-empty-function, getter-return */ 'use strict'; -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - const Class = require('./Class'); const TestTemplate = require('./TestTemplate'); const User = require('./User'); class Account extends User { - constructor(id) { - super('account', id); + constructor(conn, id) { + super(conn, 'account', id); } async getTestTemplates() { @@ -22,11 +20,10 @@ class Account extends User { accountId = ?; `; - const conn = await new DatabaseConnectionPool(); - const records = await conn.runQuery(sql, [ this.id ]); + const records = await this._conn.runQuery(sql, [ this.id ]); const promises = records.map(record => { - return new TestTemplate(record.id); + return new TestTemplate(this._conn, record.id); }); const objects = await Promise.all(promises); @@ -36,17 +33,19 @@ class Account extends User { createTestTemplate(name, maxMark) { return TestTemplate.createTestTemplate( + this._conn, this.id, name, maxMark); } createClass(name, subjectId) { - return Class.createClass(this.id, name, subjectId); + return Class.createClass(this._conn, this.id, name, subjectId); } - static async createAccount(fname, oname, lname, email, password) { + static async createAccount(conn, fname, oname, lname, email, password) { return await super.createUser( + conn, 'account', fname, oname, diff --git a/lib/Class.js b/lib/Class.js index 90a8e15..4d916e1 100644 --- a/lib/Class.js +++ b/lib/Class.js @@ -3,8 +3,6 @@ const crypto = require('crypto'); -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - const Subject = require('./Subject'); const Test = require('./Test'); @@ -57,10 +55,14 @@ class Class { */ students; + #conn; + /** * @param {string} classID - The id of the class to fetch */ - constructor(classId) { + constructor(conn, classId) { + this.#conn = conn; + const sql = ` select classId as id, @@ -95,9 +97,7 @@ class Class { `; return (async () => { - const conn = await new DatabaseConnectionPool(); - - const record = await conn.runQuery(sql, [ + const record = await this.#conn.runQuery(sql, [ classId ]); @@ -108,16 +108,14 @@ class Class { this[k] = v; const [ teacherRes, studentRes ] = await Promise.all([ - conn.runQuery(teacherSQL, [ + this.#conn.runQuery(teacherSQL, [ classId ]), - conn.runQuery(studentSQL, [ + this.#conn.runQuery(studentSQL, [ classId ]) ]); - conn.close(); - this.teacherIds = teacherRes.map(record => record.id); this.studentIds = studentRes.map(record => record.id); @@ -140,7 +138,7 @@ class Class { } getSubject() { - return new (require('./Subject'))(this.subjectId); + return new (require('./Subject'))(this.#conn, this.subjectId); } getUsers(ids, type) { @@ -153,7 +151,8 @@ class Class { throw new Error('Invalid type'); const promises = ids.map(id => { - return new (require(`./${types[type]}`))(id); + const classFile = `./${types[type]}`; + return new (require(classFile))(this.#conn, id); }); return Promise.all(promises); @@ -190,11 +189,10 @@ class Class { sql += `order by t.testDate ${order};`; - const conn = await new DatabaseConnectionPool(); - const tests = await conn.runQuery(sql, [ this.id ]); + const tests = await this.#conn.runQuery(sql, [ this.id ]); const testObjects = tests.map(test => { - return new Test(test.testId); + return new Test(this.#conn, test.testId); }); return await Promise.all(testObjects); @@ -220,14 +218,12 @@ class Class { } - static async createClass(accountId, name, subjectId) { - const s = await new Subject(subjectId); - const a = await new (require('./Account'))(accountId); + static async createClass(conn, accountId, name, subjectId) { + const s = await new Subject(conn, subjectId); + const a = await new (require('./Account'))(conn, accountId); const id = crypto.randomUUID(); - const conn = await new DatabaseConnectionPool(); - let sql = ` insert into class( classId, @@ -256,7 +252,7 @@ class Class { id ]); - return new Class(id); + return new Class(conn, id); } } diff --git a/lib/Parent.js b/lib/Parent.js index 2b651a0..c4ab062 100644 --- a/lib/Parent.js +++ b/lib/Parent.js @@ -4,16 +4,17 @@ const User = require('./User'); class Parent extends User { - constructor(id) { - super('parent', id); + constructor(conn, id) { + super(conn, 'parent', id); } get children() { } - static async createParent(fname, oname, lname, email, password) { + static async createParent(conn, fname, oname, lname, email, password) { return await super.createUser( + conn, 'parent', fname, oname, diff --git a/lib/PasswordReset.js b/lib/PasswordReset.js index 67b6304..906315d 100644 --- a/lib/PasswordReset.js +++ b/lib/PasswordReset.js @@ -3,8 +3,6 @@ const bcrypt = require('bcrypt'); const crypto = require('crypto'); -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - class PasswordReset { userId; @@ -14,7 +12,11 @@ class PasswordReset { expires; - constructor(userId, token) { + #conn; + + constructor(conn, userId, token) { + this.#conn = conn; + const sql = ` select userId, @@ -29,14 +31,11 @@ class PasswordReset { `; return (async () => { - const conn = await new DatabaseConnectionPool(); - const record = await conn.runQuery(sql, [ + const record = await this.#conn.runQuery(sql, [ userId, token ]); - conn.close(); - if (!record.length) throw new Error('No password reset found'); @@ -48,7 +47,7 @@ class PasswordReset { } get user() { - return new (require('./User'))(null, this.userId); + return new (require('./User'))(this.#conn, null, this.userId); } static async hashToken(u) { @@ -63,9 +62,8 @@ class PasswordReset { return [ nonce, token ]; } - static async generatePasswordReset(userId) { - const u = await new (require('./User'))(null, userId); - const conn = await new DatabaseConnectionPool(); + static async generatePasswordReset(conn, userId) { + const u = await new (require('./User'))(conn, null, userId); let sql = ` delete from passwordReset @@ -101,9 +99,7 @@ class PasswordReset { if (!result) throw new Error('Could not create password reset'); - conn.close(); - - return new PasswordReset(u.id, token); + return new PasswordReset(conn, u.id, token); } } diff --git a/lib/Student.js b/lib/Student.js index 9f53ba9..0ed9301 100644 --- a/lib/Student.js +++ b/lib/Student.js @@ -4,8 +4,8 @@ const User = require('./User'); class Student extends User { - constructor(id) { - super('student', id); + constructor(conn, id) { + super(conn, 'student', id); } get classes() { @@ -16,8 +16,9 @@ class Student extends User { } - static async createStudent(fname, oname, lname, email, password) { + static async createStudent(conn, fname, oname, lname, email, password) { return await super.createUser( + conn, 'student', fname, oname, diff --git a/lib/Subject.js b/lib/Subject.js index 1ca8020..80c9b83 100644 --- a/lib/Subject.js +++ b/lib/Subject.js @@ -1,7 +1,5 @@ 'use strict'; -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - class Subject { /** * The id of the subject @@ -15,10 +13,14 @@ class Subject { */ name; + #conn; + /** * @param {number} subjectID - The id of the subject to fetch */ - constructor(subjectId) { + constructor(conn, subjectId) { + this.#conn = conn; + const sql = ` select subjectId as id, @@ -30,13 +32,10 @@ class Subject { `; return (async () => { - const conn = await new DatabaseConnectionPool(); - const record = await conn.runQuery(sql, [ + const record = await this.#conn.runQuery(sql, [ subjectId, ]); - conn.close(); - if (!record.length) throw new Error('No subject found'); @@ -47,7 +46,7 @@ class Subject { })(); } - static async getAllSubjects() { + static async getAllSubjects(conn) { const sql = ` select subjectId as id @@ -55,14 +54,12 @@ class Subject { subject; `; - const conn = await new DatabaseConnectionPool(); const records = await conn.runQuery(sql); - conn.close(); const objectPromises = []; records.forEach(record => { - objectPromises.push(new Subject(record.id)); + objectPromises.push(new Subject(conn, record.id)); }); const objects = await Promise.all(objectPromises); diff --git a/lib/Test.js b/lib/Test.js index 618a33c..e807e88 100644 --- a/lib/Test.js +++ b/lib/Test.js @@ -1,9 +1,6 @@ /* eslint-disable no-empty-function, getter-return */ 'use strict'; -// Import user defined modules -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - /** * A class that represents the date of a test */ @@ -62,10 +59,14 @@ class Test { */ date; + #conn; + /** * @param {string} testId - The id of the test to fetch */ - constructor(testId) { + constructor(conn, testId) { + this.#conn = conn; + const sql = ` select testId as id, @@ -79,13 +80,10 @@ class Test { `; return (async () => { - const conn = await new DatabaseConnectionPool(); - const record = await conn.runQuery(sql, [ + const record = await this.#conn.runQuery(sql, [ testId, ]); - conn.close(); - if (!record.length) throw new Error('No test found'); @@ -107,11 +105,14 @@ class Test { } getClass() { - return new (require('./Class'))(this.classId); + return new (require('./Class'))(this.#conn, this.classId); } getTestTemplate() { - return new (require('./TestTemplate'))(this.templateId); + return new (require('./TestTemplate'))( + this.#conn, + this.templateId + ); } async hasAccess(u) { diff --git a/lib/TestTemplate.js b/lib/TestTemplate.js index 073821f..9c45444 100644 --- a/lib/TestTemplate.js +++ b/lib/TestTemplate.js @@ -5,7 +5,6 @@ const crypto = require('crypto'); // Import user defined modules const Class = require('./Class'); -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); const Test = require('./Test'); /** @@ -42,10 +41,14 @@ class TestTemplate { */ maxMark; + #conn; + /** * @param {string} testTemplateId - The id of the template to fetch */ - constructor(testTemplateId) { + constructor(conn, testTemplateId) { + this.#conn = conn; + const sql = ` select testTemplateId as id, @@ -59,13 +62,10 @@ class TestTemplate { `; return (async () => { - const conn = await new DatabaseConnectionPool(); - const record = await conn.runQuery(sql, [ + const record = await this.#conn.runQuery(sql, [ testTemplateId, ]); - conn.close(); - if (!record.length) throw new Error('No test template found'); @@ -79,11 +79,11 @@ class TestTemplate { } getAccount() { - return new (require('./Account'))(this.accountId); + return new (require('./Account'))(this.#conn, this.accountId); } async assignClass(classId, date) { - const c = await new Class(classId); + const c = await new Class(this.#conn, classId); const id = crypto.randomUUID(); const epochDate = date.getTime() / 1000; @@ -97,34 +97,25 @@ class TestTemplate { (?, ?, ?, FROM_UNIXTIME(?)); `; - const conn = await new DatabaseConnectionPool(); - - const result = await conn.runQuery(sql, [ + await this.#conn.runQuery(sql, [ id, this.id, c.id, epochDate ]); - conn.close(); - - if (!result.length) - throw new Error('Could not assign class'); - - return new Test(id); + return new Test(this.#conn, id); } get classes() { } - static async createTestTemplate(accountId, name, maxMark) { - const a = await new (require('./Account'))(accountId); + static async createTestTemplate(conn, accountId, name, maxMark) { + const a = await new (require('./Account'))(conn, accountId); const id = crypto.randomUUID(); - const conn = await new DatabaseConnectionPool(); - const sql = ` insert into testTemplate( testTemplateId, @@ -142,12 +133,10 @@ class TestTemplate { maxMark ]); - conn.close(); - if (!result) throw new Error('Could not create test template'); - return new TestTemplate(id); + return new TestTemplate(conn, id); } } diff --git a/lib/User.js b/lib/User.js index 9178689..77c2622 100644 --- a/lib/User.js +++ b/lib/User.js @@ -3,8 +3,6 @@ const bcrypt = require('bcrypt'); const crypto = require('crypto'); -const DatabaseConnectionPool = require('./DatabaseConnectionPool'); - const Class = require('./Class'); const PasswordReset = require('./PasswordReset'); const Test = require('./Test'); @@ -28,9 +26,13 @@ class User { type = null; - constructor(type, userId) { + _conn; + + constructor(conn, type, userId) { type = type ?? false; + this._conn = conn; + let types = []; let knownType = false; if (type) { @@ -41,7 +43,6 @@ class User { } return (async () => { - const conn = await new DatabaseConnectionPool(); const queryPromises = []; for (const type of types) { @@ -59,13 +60,12 @@ class User { ${type}Id = ?; `; - queryPromises.push(conn.runQuery(sql, [ + queryPromises.push(this._conn.runQuery(sql, [ userId ])); } const typeResults = await Promise.all(queryPromises); - conn.close(); for (const [ i, result ] of typeResults.entries()) { if (!result.length) @@ -92,7 +92,10 @@ class User { `${type.substring(0, 1).toUpperCase()}`+ `${type.substring(1)}`; - return new (require(`./${className}`))(this.id); + return new (require(`./${className}`))( + this._conn, + this.id + ); } throw new Error('No user found'); @@ -121,8 +124,6 @@ class User { async changePassword(password) { const newPassword = await User.hashPassword(password); - const conn = await new DatabaseConnectionPool(); - const sql = ` update ${this.type} @@ -132,11 +133,11 @@ class User { ${this.type}Id = ?; `; - await conn.runQuery(sql, [ newPassword, this.id ]); + await this._conn.runQuery(sql, [ newPassword, this.id ]); } generatePasswordReset() { - return PasswordReset.generatePasswordReset(this.id); + return PasswordReset.generatePasswordReset(this._conn, this.id); } login(req) { @@ -180,11 +181,10 @@ class User { sql += `order by t.testDate ${order};`; - const conn = await new DatabaseConnectionPool(); - const tests = await conn.runQuery(sql, [ this.id ]); + const tests = await this._conn.runQuery(sql, [ this.id ]); const testObjects = tests.map(test => { - return new Test(test.testId); + return new Test(this._conn, test.testId); }); return await Promise.all(testObjects); @@ -206,11 +206,10 @@ class User { c.name; `; - const conn = await new DatabaseConnectionPool(); - const classes = await conn.runQuery(sql, [ this.id ]); + const classes = await this._conn.runQuery(sql, [ this.id ]); const classObjects = classes.map(c => { - return new Class(c.classId); + return new Class(this._conn, c.classId); }); return await Promise.all(classObjects); @@ -220,9 +219,15 @@ class User { return await bcrypt.hash(password, 10); } - static async createUser(type, fname, oname, lname, email, password) { - const conn = await new DatabaseConnectionPool(); - + static async createUser( + conn, + type, + fname, + oname, + lname, + email, + password + ) { const uuid = crypto.randomUUID(); const hashedPassword = await User.hashPassword(password); @@ -250,7 +255,7 @@ class User { let res; switch (type) { case 'account': - res = new (require('./Account'))(uuid); + res = new (require('./Account'))(conn, uuid); break; default: throw new Error( @@ -260,8 +265,7 @@ class User { return res; } - static async getUserByEmail(email) { - const conn = await new DatabaseConnectionPool(); + static async getUserByEmail(conn, email) { const types = [ 'account', 'student', 'parent' ]; const idPromises = []; @@ -291,7 +295,10 @@ class User { const className = `${type.substring(0, 1).toUpperCase()}`+ `${type.substring(1)}`; - return new (require(`./${className}`))(id); + return new (require(`./${className}`))( + conn, + id + ); } } } diff --git a/routes/admin.js b/routes/admin.js index 41db4b0..85f16b1 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -13,7 +13,7 @@ router.get('/', (req, res) => { }); router.get('/dashboard', async (req, res) => { - const u = await new User(null, req.session.userId); + const u = await new User(req.db, null, req.session.userId); const recentTests = await u.getTests({ range: 'before' }); const upcomingTests = await u.getTests({ range: 'after' }); @@ -52,7 +52,7 @@ router.get('/dashboard', async (req, res) => { router.all(/user\/(.{36})(\/.*)?/, async (req, res, next) => { let u; try { - u = await new User(null, req.params[0]); + u = await new User(req.db, null, req.params[0]); } catch (e) { return res.status(400).render('error', { title: 'Stratos - Error', @@ -63,14 +63,18 @@ router.all(/user\/(.{36})(\/.*)?/, async (req, res, next) => { }); } - if (!await u.hasAccess(await new User(null, req.session.userId))) + if (!await u.hasAccess(await new User( + req.db, + null, + req.session.userId + ))) return res.redirect('/admin/dashboard'); next(); }); router.get('/user/:id', async (req, res) => { - const u = await new User(null, req.params.id); + const u = await new User(req.db, null, req.params.id); return res.render('user', { title: `Stratos - ${u.shortName}`, diff --git a/routes/class.js b/routes/class.js index 376b179..54e82a3 100644 --- a/routes/class.js +++ b/routes/class.js @@ -11,7 +11,7 @@ const User = require('../lib/User'); const Subject = require('../lib/Subject'); router.get('/classes', async (req, res) => { - const u = await new User(null, req.session.userId); + const u = await new User(req.db, null, req.session.userId); return res.render('classes', { title: 'Stratos - Classes', @@ -23,7 +23,7 @@ router.get('/classes', async (req, res) => { }); router.get('/class/add', async (req, res) => { - const subjects = await Subject.getAllSubjects(); + const subjects = await Subject.getAllSubjects(req.db); res.render('addClass', { title: 'Stratos - Add class', @@ -34,7 +34,7 @@ router.get('/class/add', async (req, res) => { }); router.post('/class/add', async (req, res) => { - const a = await new Account(req.session.userId); + const a = await new Account(req.db, req.session.userId); let fields; try { @@ -59,7 +59,7 @@ router.post('/class/add', async (req, res) => { router.all(/class\/(.{36})(\/.*)?/, async (req, res, next) => { let c; try { - c = await new Class(req.params[0]); + c = await new Class(req.db, req.params[0]); } catch (e) { return res.status(400).render('error', { title: 'Stratos - Error', @@ -70,14 +70,17 @@ router.all(/class\/(.{36})(\/.*)?/, async (req, res, next) => { }); } - if (!await c.hasAccess(await new User(null, req.session.userId))) + if (!await c.hasAccess(await new User(req.db, + null, + req.session.userId + ))) return res.redirect('/admin/classes'); next(); }); router.get('/class/:id', async (req, res) => { - const c = await new Class(req.params.id); + const c = await new Class(req.db, req.params.id); const linkRoot = `/admin/class/${c.id}`; const upcomingTests = await c.getTests({ range: 'after' }); const recentTests = await c.getTests({ range: 'before' }); @@ -127,7 +130,7 @@ router.get('/class/:id', async (req, res) => { }); router.get('/class/:id/teachers', async (req, res) => { - const c = await new Class(req.params.id); + const c = await new Class(req.db, req.params.id); return res.render('classUsers', { title: `Stratos - ${c.name}`, @@ -143,7 +146,7 @@ router.get('/class/:id/teachers', async (req, res) => { }); router.get('/class/:id/members', async (req, res) => { - const c = await new Class(req.params.id); + const c = await new Class(req.db, req.params.id); return res.render('classUsers', { title: `Stratos - ${c.name}`, diff --git a/routes/main.js b/routes/main.js index df5efa6..df1f250 100644 --- a/routes/main.js +++ b/routes/main.js @@ -69,7 +69,10 @@ router.post('/login', async (req, res) => { return res.status(400).json({ status: 'Invalid' }); } - const u = await User.getUserByEmail(fields.get('email')) ?? false; + const u = await User.getUserByEmail( + req.db, + fields.get('email') + ) ?? false; if (!u) return res.redirect('/login'); @@ -106,6 +109,7 @@ router.post('/register', async (req, res) => { let a; try { a = await Account.createAccount( + req.db, fields.get('fname'), fields.get('onames'), fields.get('lname'), @@ -140,7 +144,10 @@ router.post('/password-reset', async (req, res) => { console.error(e); } - const u = await User.getUserByEmail(fields.get('email')) ?? false; + const u = await User.getUserByEmail( + req.db, + fields.get('email') + ) ?? false; if (!u) return res.redirect('/password-reset'); @@ -170,7 +177,7 @@ router.get('/password-reset/:uuid/:token', async (req, res) => { let pr; try { - pr = await new PasswordReset(uuid, token); + pr = await new PasswordReset(req.db, uuid, token); } catch (e) { console.error(e); return res.redirect('/password-reset'); @@ -211,6 +218,7 @@ router.post('/change-password', async (req, res) => { let pr; try { pr = await new PasswordReset( + req.db, fields.get('uuid'), fields.get('token') ); diff --git a/routes/test.js b/routes/test.js index 2ac4aee..a19bccf 100644 --- a/routes/test.js +++ b/routes/test.js @@ -10,7 +10,7 @@ const User = require('../lib/User'); const Test = require('../lib/Test'); router.get('/tests', async (req, res) => { - const u = await new User(null, req.session.userId); + const u = await new User(req.db, null, req.session.userId); return res.render('tests', { title: 'Stratos - Tests', @@ -22,7 +22,7 @@ router.get('/tests', async (req, res) => { }); router.get('/test/add', async (req, res) => { - const a = await new Account(req.session.userId); + const a = await new Account(req.db, req.session.userId); const promises = [ a.getTestTemplates(), @@ -58,7 +58,10 @@ router.post('/test/add', async (req, res) => { } const testTemplateId = fields.get('testTemplate'); - const tt = await new (require('../lib/TestTemplate'))(testTemplateId); + const tt = await new (require('../lib/TestTemplate'))( + req.db, + testTemplateId + ); const t = await tt.assignClass( fields.get('class'), @@ -76,7 +79,7 @@ router.get('/testTemplate/add', (req, res) => { }); router.post('/testTemplate/add', async (req, res) => { - const a = await new Account(req.session.userId); + const a = await new Account(req.db, req.session.userId); let fields; try { @@ -94,8 +97,11 @@ router.post('/testTemplate/add', async (req, res) => { try { await a.createTestTemplate( fields.get('name'), - fields.get('mark')); + fields.get('mark') + ); } catch (e) { + console.error(e); + return res.render('error', { title: 'Stratos - Error', current: 'Tests', @@ -110,7 +116,7 @@ router.post('/testTemplate/add', async (req, res) => { router.all(/test\/(.{36})(\/.*)?/, async (req, res, next) => { let t; try { - t = await new Test(req.params[0]); + t = await new Test(req.db, req.params[0]); } catch (e) { return res.status(400).render('error', { title: 'Stratos - Error', @@ -121,14 +127,17 @@ router.all(/test\/(.{36})(\/.*)?/, async (req, res, next) => { }); } - if (!await t.hasAccess(await new User(null, req.session.userId))) + if (!await t.hasAccess(await new User( + req.db, null, + req.session.userId + ))) return res.redirect('/admin/tests'); next(); }); router.get('/test/:id', async (req, res) => { - const t = await new Test(req.params.id); + const t = await new Test(req.db, req.params.id); const linkRoot = `/admin/test/${t.id}`; return res.render('test', {