1
0
Fork 0
chronos/server/database.js

256 lines
7.0 KiB
JavaScript

import mysql from 'mysql';
import semver from 'semver';
import { fatal, getVersion } from './utils';
import { NotFoundError } from './errors';
const DATABASE = 'chronos';
export default class Database {
constructor(options) {
this.connection = mysql.createConnection(options);
this.connection.connect();
this.checkAndMigrate();
}
async getSchools() {
return this.query(`
SELECT id, name, domain
FROM school
`);
}
async getSchoolWithAuth(id) {
return this.query(`
SELECT *
FROM auth RIGHT JOIN school
ON auth.school = school.id
WHERE school.${isNaN(parseInt(id, 10)) ? 'domain' : 'id'} = ?
`, [id], {
required: true,
})
.then(results => ({
id: results[0].id,
name: results[0].name,
domain: results[0].domain,
auth: results[0].type ? results.map(r => Object.assign(r, {
school: undefined,
name: undefined,
domain: undefined,
})) : [],
}))
.catch(err => Promise.reject(err.withNoun('School')));
}
// eslint-disable-next-line class-methods-use-this
async getUsers(school) {
return this.query(`
SELECT id, name
FROM user
WHERE user.school = ?
`, [school]);
}
async getUser(id) {
return this.query(`
SELECT *
FROM user
WHERE user.id = ?
`, [id], {
single: true,
})
.catch(err => Promise.reject(err.withNoun('User')));
}
async getUserWithSchool(school, id) {
return this.query(`
SELECT *
FROM user
WHERE user.school = ?
AND user.id = ?
`, [school, id], {
single: true,
})
.catch(err => Promise.reject(err.withNoun('User')));
}
async getUserByEmail(email) {
// assumes unique email
return this.query(`
SELECT *
FROM user
WHERE user.email = ?
`, [email], {
single: true,
})
.catch(err => Promise.reject(err.withNoun('User')));
}
query(query, values, options = {}) {
console.log('QUERY:', query.replace(/[\n\t]+/g, ' ').replace(/^ /g, '').replace(/ $/g, ''));
return new Promise((resolve, reject) => {
this.connection.query(query, values, (err, results, fields) => {
if (err) {
reject(err);
} else if ((options.required || options.single) && results.length < 1) {
reject(new NotFoundError());
}
if (options.single) {
resolve(results[0], fields);
} else {
resolve(results, fields);
}
});
});
}
async checkAndMigrate() {
return this.query(`CREATE DATABASE IF NOT EXISTS ${DATABASE}`)
.then(() => this.query(`USE ${DATABASE}`))
.then(() => this.query('SELECT * FROM options WHERE option = ?', ['VERSION'])
.then(result => this.migrate(result[0] ? result[0].value : null))
.catch(() => this.migrate()))
.catch(err => fatal(err));
}
async migrate(oldVersion) {
if (semver.satisfies(oldVersion, '~1')) {
// database is up-to-date
return true;
} else if (semver.satisfies(oldVersion, '~0')) { // lmao forces database migration
return true;
// database needs to be updated
// return this.query(`DROP DATABASE ${DATABASE}`)
// .then(() => this.checkAndMigrate());
}
// database does not exist
// create tables
const tables = [
`CREATE TABLE options (
option VARCHAR(12) NOT NULL,
value VARCHAR(64) NOT NULL,
PRIMARY KEY (option)
)`,
`CREATE TABLE school (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
domain VARCHAR(32),
PRIMARY KEY (id)
)`,
`CREATE TABLE auth (
school INT NOT NULL,
id INT NOT NULL,
type CHAR(3) NOT NULL,
oid_meta VARCHAR(128),
oid_cid VARCHAR(64),
oid_csecret VARCHAR(64),
PRIMARY KEY (school, id),
FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE holiday (
id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
PRIMARY KEY (id)
)`,
`CREATE TABLE observes (
school INT NOT NULL,
holiday INT NOT NULL,
FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (holiday) REFERENCES holiday(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE user (
school INT NOT NULL,
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(64),
email VARCHAR(64),
oid_id VARCHAR(64),
pwd_hash VARCHAR(64),
role CHAR(3),
PRIMARY KEY (id),
FOREIGN KEY (school) REFERENCES school(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE group_ (
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(64) NOT NULL,
type CHAR(3),
PRIMARY KEY (id)
)`,
`CREATE TABLE group_mentor (
id INT NOT NULL,
level TINYINT NOT NULL,
year YEAR(4) NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (id) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE member (
user INT NOT NULL,
group_ INT NOT NULL,
FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE event_once (
group_ INT NOT NULL,
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(64) NOT NULL,
start DATETIME NOT NULL,
end DATETIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE attachment (
event_once INT NOT NULL,
id INT NOT NULL,
PRIMARY KEY (event_once, id),
FOREIGN KEY (event_once) REFERENCES event_once(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE event_weekly (
group_ INT NOT NULL,
id INT AUTO_INCREMENT NOT NULL,
name VARCHAR(64) NOT NULL,
day TINYINT NOT NULL,
starttime TIME NOT NULL,
endtime TIME NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (group_) REFERENCES group_(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
`CREATE TABLE ignored (
user INT NOT NULL,
event_weekly INT NOT NULL,
FOREIGN KEY (user) REFERENCES user(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (event_weekly) REFERENCES event_weekly(id) ON DELETE CASCADE ON UPDATE CASCADE
)`,
];
for (let i = 0; i < tables.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
await this.query(tables[i]);
}
// set version number
await this.query(`
INSERT INTO options (option, value)
VALUES ('version', '${getVersion()}')
`);
// TODO: Build admin interface to add schools and school owner
// add first school
const firstSchool = await this.query(`
INSERT INTO school (name, domain)
VALUES (?, ?)
`, ['NUS High School', 'nushigh.edu.sg']);
await this.query(`
INSERT INTO user (school, name, email, pwd_hash, role)
VALUES (?, ?, ?, ?, ?)
`, [firstSchool.insertId, 'Ambrose Chua', 'h1310031@nushigh.edu.sg', '', 'OWN']);
// eslint-disable-next-line global-require
const fs = require('fs');
const tmpsettings = JSON.parse(fs.readFileSync('oid_settings.json', 'utf8'));
await this.query(`
INSERT INTO auth (school, type, oid_meta, oid_cid, oid_csecret)
VALUES (?, ?, ?, ?, ?)
`, [firstSchool.insertId, 'OID', tmpsettings.oid_meta, tmpsettings.oid_cid, tmpsettings.oid_csecret]);
return true;
}
}