mirror of
https://github.com/matt-fidd/stratos.git
synced 2026-01-01 17:59:25 +00:00
321 lines
5.6 KiB
JavaScript
321 lines
5.6 KiB
JavaScript
'use strict';
|
|
|
|
const bcrypt = require('bcrypt');
|
|
const crypto = require('crypto');
|
|
|
|
const Class = require('./Class');
|
|
const PasswordReset = require('./PasswordReset');
|
|
const Test = require('./Test');
|
|
|
|
class User {
|
|
id;
|
|
|
|
firstName;
|
|
|
|
otherNames;
|
|
|
|
lastName;
|
|
|
|
shortName;
|
|
|
|
fullName;
|
|
|
|
email;
|
|
|
|
#password;
|
|
|
|
type = null;
|
|
|
|
_conn;
|
|
|
|
constructor(conn, type, userId) {
|
|
type = type ?? false;
|
|
|
|
this._conn = conn;
|
|
|
|
let types = [];
|
|
let knownType = false;
|
|
if (type) {
|
|
types.push(type);
|
|
knownType = true;
|
|
} else {
|
|
types = [ 'account', 'student', 'parent' ];
|
|
}
|
|
|
|
return (async () => {
|
|
const queryPromises = [];
|
|
|
|
for (const type of types) {
|
|
const sql = `
|
|
select
|
|
email,
|
|
firstName,
|
|
otherNames,
|
|
lastName,
|
|
password,
|
|
${type}Id as id
|
|
from
|
|
${type}
|
|
where
|
|
${type}Id = ?;
|
|
`;
|
|
|
|
queryPromises.push(this._conn.runQuery(sql, [
|
|
userId
|
|
]));
|
|
}
|
|
|
|
const typeResults = await Promise.all(queryPromises);
|
|
|
|
for (const [ i, result ] of typeResults.entries()) {
|
|
if (!result.length)
|
|
continue;
|
|
|
|
const record = result[0];
|
|
const type = types[i];
|
|
|
|
for (const [ k, v ] of Object.entries(record)) {
|
|
if (k === 'password')
|
|
this.#password = v;
|
|
else
|
|
this[k] = v;
|
|
}
|
|
|
|
this.type = type;
|
|
this.shortName = this.getShortName();
|
|
this.fullName = this.getFullName();
|
|
|
|
if (knownType)
|
|
return this;
|
|
|
|
const className =
|
|
`${type.substring(0, 1).toUpperCase()}`+
|
|
`${type.substring(1)}`;
|
|
|
|
return new (require(`./${className}`))(
|
|
this._conn,
|
|
this.id
|
|
);
|
|
}
|
|
|
|
throw new Error('No user found');
|
|
})();
|
|
}
|
|
|
|
getFullName() {
|
|
let name = `${this.firstName} `;
|
|
|
|
if (this?.otherNames?.length > 0)
|
|
name += `${this.otherNames} `;
|
|
|
|
name += this.lastName;
|
|
|
|
return name;
|
|
}
|
|
|
|
getShortName() {
|
|
return `${this.firstName} ${this.lastName}`;
|
|
}
|
|
|
|
async verifyPassword(password) {
|
|
return await bcrypt.compare(password, this.#password);
|
|
}
|
|
|
|
async changePassword(password) {
|
|
const newPassword = await User.hashPassword(password);
|
|
|
|
const sql = `
|
|
update
|
|
${this.type}
|
|
set
|
|
password = ?
|
|
where
|
|
${this.type}Id = ?;
|
|
`;
|
|
|
|
await this._conn.runQuery(sql, [ newPassword, this.id ]);
|
|
}
|
|
|
|
generatePasswordReset() {
|
|
return PasswordReset.generatePasswordReset(this._conn, this.id);
|
|
}
|
|
|
|
login(req) {
|
|
req.session.authenticated = true;
|
|
req.session.userId = this.id;
|
|
req.session.userType = this.type;
|
|
req.session.fullName = `${this.firstName} ${this.lastName}`;
|
|
}
|
|
|
|
async getTests({ order='desc', range='all' } = {}) {
|
|
const validOrder = [ 'asc', 'desc' ];
|
|
const validRange = [ 'all', 'before', 'after' ];
|
|
|
|
if (this.type === 'parent')
|
|
throw new Error(`Can not fetch tests for ${this.type}`);
|
|
|
|
if (!validOrder.includes(order))
|
|
throw new Error('Invalid order');
|
|
|
|
if (!validRange.includes(range))
|
|
throw new Error('Invalid range');
|
|
|
|
let sql = `
|
|
select
|
|
t.testId as testId
|
|
from
|
|
test t
|
|
join ${this.type}ClassLink link using(classId)
|
|
where
|
|
link.${this.type}Id = ?
|
|
`;
|
|
|
|
switch (range) {
|
|
case 'before':
|
|
sql += 'and t.testDate <= curdate()';
|
|
break;
|
|
case 'after':
|
|
sql += 'and t.testDate > curdate()';
|
|
break;
|
|
}
|
|
|
|
sql += `order by t.testDate ${order};`;
|
|
|
|
const tests = await this._conn.runQuery(sql, [ this.id ]);
|
|
|
|
const testObjects = tests.map(test => {
|
|
return new Test(this._conn, test.testId);
|
|
});
|
|
|
|
return await Promise.all(testObjects);
|
|
}
|
|
|
|
async getClasses() {
|
|
if (this.type === 'parent')
|
|
throw new Error(`Can not fetch class for ${this.type}`);
|
|
|
|
const sql = `
|
|
select
|
|
c.classId as classId
|
|
from
|
|
class c
|
|
join ${this.type}ClassLink link using(classId)
|
|
where
|
|
link.${this.type}Id = ?
|
|
order by
|
|
c.name;
|
|
`;
|
|
|
|
const classes = await this._conn.runQuery(sql, [ this.id ]);
|
|
|
|
const classObjects = classes.map(c => {
|
|
return new Class(this._conn, c.classId);
|
|
});
|
|
|
|
return await Promise.all(classObjects);
|
|
}
|
|
|
|
static async hashPassword(password) {
|
|
return await bcrypt.hash(password, 10);
|
|
}
|
|
|
|
static async createUser(
|
|
conn,
|
|
type,
|
|
fname,
|
|
oname,
|
|
lname,
|
|
email,
|
|
password
|
|
) {
|
|
const uuid = crypto.randomUUID();
|
|
const hashedPassword = await User.hashPassword(password);
|
|
|
|
const sql = `
|
|
insert into ${type}(
|
|
${type}Id,
|
|
email,
|
|
firstName,
|
|
otherNames,
|
|
lastName,
|
|
password)
|
|
values
|
|
(?, ?, ?, ?, ?, ?);
|
|
`;
|
|
|
|
await conn.runQuery(sql, [
|
|
uuid,
|
|
email,
|
|
fname,
|
|
oname,
|
|
lname,
|
|
hashedPassword
|
|
]);
|
|
|
|
let res;
|
|
switch (type) {
|
|
case 'account':
|
|
res = new (require('./Account'))(conn, uuid);
|
|
break;
|
|
default:
|
|
throw new Error(
|
|
`Cannot create user of type ${type}`);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static async getUserByEmail(conn, email) {
|
|
const types = [ 'account', 'student', 'parent' ];
|
|
|
|
const idPromises = [];
|
|
|
|
for (const type of types) {
|
|
const sql = `
|
|
select
|
|
${type}id as id
|
|
from
|
|
${type}
|
|
where
|
|
email = ?;
|
|
`;
|
|
|
|
idPromises.push(conn.runQuery(sql, [
|
|
email
|
|
]));
|
|
}
|
|
|
|
const ids = await Promise.all(idPromises);
|
|
|
|
for (const [ i, records ] of ids.entries()) {
|
|
const type = types[i];
|
|
const id = records?.[0]?.id;
|
|
|
|
if (typeof id !== 'undefined') {
|
|
const className =
|
|
`${type.substring(0, 1).toUpperCase()}`+
|
|
`${type.substring(1)}`;
|
|
return new (require(`./${className}`))(
|
|
conn,
|
|
id
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
async hasAccess(u) {
|
|
let [ userClasses, thisClasses ] = await Promise.all([
|
|
u.getClasses(),
|
|
this.getClasses()
|
|
]);
|
|
|
|
userClasses = userClasses.map(c => c.id);
|
|
|
|
return thisClasses.filter(c =>
|
|
userClasses.includes(c.id)
|
|
).length > 0;
|
|
}
|
|
}
|
|
|
|
module.exports = User;
|