adejs/utils/model.js

343 lines
8.4 KiB
JavaScript

const fs = require('fs');
const db = require('db');
const log = require('log');
const bc = require('bcryptjs');
let models = [];
function postgresToCamel(postgres) {
let ret = '';
for (let i = 0; i < postgres.length; i++) {
let c = postgres.charAt(i);
if (c === '_') {
i++;
ret += postgres.charAt(i).toUpperCase();
} else {
ret += c;
}
}
return ret;
}
function camelToPostgres(camelCase) {
let ret = '';
for (let i = 0; i < camelCase.length; i++) {
let c = camelCase.charAt(i);
if (c === c.toUpperCase()) {
ret += '_' + c.toLowerCase()
} else {
ret += c;
}
}
return ret;
}
function attributeToGetBy(attributeName) {
return 'getBy' +
attributeName.charAt(0).toUpperCase() + attributeName.substr(1);
}
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);
let query =
"DROP TABLE IF EXISTS " +
camelToPostgres(this.name) + " CASCADE;";
db.query(query)
.then(res => this.createTable(callback))
.catch(err => log.dberror(err));
}
createTable(callback) {
let query = 'CREATE TABLE ' + camelToPostgres(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 " + camelToPostgres(this.name) + " created")
callback();
})
.catch(err => log.dberror(err));
}
}
module.exports.BaseField = class {
constructor(name, properties = {}) {
this.name = name;
this.unique = properties.unique || false;
this.notNull = properties.notNull || false;
this.default = properties.default;
}
getCreationString() {
return [
camelToPostgres(this.name),
this.getPostgresType(),
this.getFormattedPropeties(),
].join(' ');
}
getFormattedPropeties() {
let properties = [];
if (this.unique) {
properties.push('UNIQUE');
}
if (this.notNull) {
properties.push('NOT NULL');
}
return properties.join(' ');
}
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, properties = {}) {
super(name, properties);
}
getPostgresType() {
return "SMALLINT";
}
}
module.exports.SerialField = class extends module.exports.BaseField {
constructor(name, properties = {}) {
super(name, properties);
this.unique = true;
}
getPostgresType() {
return "SERIAL";
}
}
module.exports.TextField = class extends module.exports.BaseField {
constructor(name, properties = {}) {
super(name, properties);
}
getPostgresType() {
return "TEXT";
}
}
module.exports.PasswordField = class extends module.exports.BaseField {
constructor(name, properties = {}) {
super(name, properties);
}
getPostgresType() {
return '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.BoolField = class extends module.exports.BaseField {
constructor(name, properties) {
super(name, properties);
}
getPostgresType() {
return 'BOOLEAN';
}
}
module.exports.createClass = function(model) {
let ret = function(param) {
this._persisted = false;
for (let field of model.fields) {
this['_' + field.name] = {
value: field.default,
changed: true,
}
Object.defineProperty(this, field.name, field.createMutators());
}
};
for (let field of model.fields) {
if (!field.unique && field.name !== 'activationKey')
continue;
ret[attributeToGetBy(field.name)] = function(value, callback) {
let query =
'SELECT * FROM ' + camelToPostgres(model.name) +
' WHERE ' + camelToPostgres(field.name) +
'=$1;';
db
.query(query, [value])
.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][camelToPostgres(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 ' + camelToPostgres(model.name) + '('
let queryEnd = ' VALUES (';
queryBegin += fieldsToSave
.map(f => camelToPostgres(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 ' + camelToPostgres(model.name) + ' SET ';
query += fieldsToSave.map((f, i) => {
return camelToPostgres(f.field.name) + '=$' + (i+1);
}).join(',');
query += ' WHERE id=$' + (fieldsToSave.length + 1) + ' RETURNING *;'
db
.query(query, [...fieldsToSave.map(f => f.value), this.id])
.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);
});
}