const fs = require('fs'); const db = require('db'); const log = require('log'); const bc = require('bcryptjs'); let models = []; module.exports.BaseModel = class { constructor(name) { models.push(this); this.name = name; this.fields = []; } addField(field) { this.fields.push(field); } reinitialize(callback = () => {}) { log.debug('Reinitializing ' + this.name); db.query('DROP TABLE IF EXISTS ' + this.name + ' CASCADE') .then(res => this.createTable(callback)) .catch(err => log.dberror(err)); } createTable(callback) { let query = 'CREATE TABLE ' + this.name + '(\n'; query += this.fields .map(f => '\t' + f.getCreationString()) .join(',\n'); query += ');'; log.debug(query); db.query(query) .then(res => { log.debug("Table " + this.name + " created") callback(); }) .catch(err => log.dberror(err)); } } module.exports.BaseField = class { constructor(name) { this.name = name; } createMutators() { return { get: this.createGetter(), set: this.createSetter(), }; } createGetter() { var self = this; return function() { return this['_' + self.name].value; } } createSetter() { var self = this; return function(e) { this['_' + self.name] = { value: e, changed: true, }; } } } module.exports.SmallIntegerField = class extends module.exports.BaseField { constructor(name) { super(name); } getCreationString() { return this.name + " SMALLINT"; } } module.exports.SerialField = class extends module.exports.BaseField { constructor(name) { super(name); } getCreationString() { return this.name + " SERIAL"; } } module.exports.TextField = class extends module.exports.BaseField { constructor(name) { super(name); } getCreationString() { return this.name + " TEXT"; } } module.exports.PasswordField = class extends module.exports.BaseField { constructor(name) { super(name); } getCreationString() { return this.name + ' TEXT'; } createSetter() { let self = this; return function(e) { this['_' + self.name] = { value: bc.hashSync(e, 8), changed: true, }; } } } module.exports.PasswordField.testSync = function(password, hash) { return bc.compareSync(password, hash); } module.exports.createClass = function(model) { let ret = function(param) { this._persisted = false; for (let field of model.fields) { this['_' + field.name] = { value: undefined, changed: false, } Object.defineProperty(this, field.name, field.createMutators()); } }; ret.getById = function(id, callback) { db .query('SELECT * FROM ' + model.name + ' WHERE id=$1;', [id]) .then(res => { // Create the instance let instance = new ret(); instance._persisted = true; instance._setFromDbResult(res); callback(undefined, instance); }) .catch(err => callback(err)); } ret.prototype._setFromDbResult = function(res) { for (let field of model.fields) { this['_' + field.name] = { value: res.rows[0][field.name], changed: false, }; } } ret.prototype.save = function(callback = () => {}) { let fieldsToSave = []; for (let field of model.fields) { if (this['_' + field.name].changed) { fieldsToSave.push({ field: field, value: this[field.name], }); } } fieldsToSave = fieldsToSave.filter((field) => { return !(field.field instanceof module.exports.SerialField); }); if (!this._persisted) { // INSERT INTO let queryBegin = 'INSERT INTO ' + model.name + '(' let queryEnd = ' VALUES ('; queryBegin += fieldsToSave .map(f => f.field.name) .join(','); queryEnd += fieldsToSave .map((f, id) => '$' + (id + 1)) .join(','); queryBegin += ')' queryEnd += ') RETURNING *;' let query = queryBegin + queryEnd; db .query(query, fieldsToSave.map(f => f.value)) .then(res => { this._setFromDbResult(res); this._persisted = true; callback(undefined, this); }) .catch(err => callback(err)); } else { // UPDATE FROM let query = 'UPDATE FROM ' + model.name + ' SET '; query += fieldsToSave.map(f => { return f.field.name + '=' + f.value; }).join(','); query += ') RETURNING *;' db .query(query, fieldsToSave.map(f => f.value)) .then(res => { this._setFromDbResult(res); this._persisted = true; callback(undefined, this); }) .catch(err => callback(err)); } } return ret; } module.exports.reinitialize = function(callback = () => {}) { let finished = models.map(m => false); for (let i = 0; i < models.length; i++) { let model = models[i]; model.reinitialize(() => { finished[i] = true; if (finished.reduce((a, b) => a && b, true)) { callback(); } }); } } module.exports.load = function(controllersDir) { fs.readdirSync(controllersDir).forEach(function(name) { let path = controllersDir + '/' + name + '/models.js'; try { fs.accessSync(path); } catch (e) { return; } require(path); }); }