343 lines
8.4 KiB
JavaScript
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);
|
|
});
|
|
|
|
}
|