CSOL-site/db.js

138 строки
4.2 KiB
JavaScript

const async = require('async');
const path = require('path');
const Sequelize = require('sequelize');
const DB_NAME = process.env['CSOL_DB_NAME'] || 'csol';
const USERNAME = process.env['CSOL_DB_USER'] || 'root';
const PASSWORD = process.env['CSOL_DB_PASS'];
const DB_HOST = process.env['CSOL_DB_HOST'];
const DB_PORT = process.env['CSOL_DB_PORT'];
const MODEL_PATH = process.env['CSOL_MODEL_PATH'] || path.join(__dirname, 'models');
const db = new Sequelize(DB_NAME, USERNAME, PASSWORD, {
host: DB_HOST,
port: DB_PORT,
define: { charset: 'utf8' }
});
/**
* In order to get useful bi-directional relationships on models, the relationship
* has to be defined on both sides:
*
* Learner.belongsTo(Guardian);
* Guardian.hasMany(Learner);
*
* This automatically adds functionality to both parties, and without both
* relationships being defined, half would be missing:
*
* <learner instance>.[get|set]Guardian();
* <guardian instance>.[get|set]Learners();
* <guardian instance>.[add|remove]Learner();
*
* However, this doesn't work when models are split out into their own files, as
* we end up with circular references. That is, in order for `Learner` to reference
* `Guardian`, it has to `require(.../guardian)`, but `Guardian` has to do likewise
* in order to reference `Learner`.
*
* Hence the following abstraction, which allows us to define models in their own
* files, but also allows for fully-defined bi-directional relationships. So,
* rather than pull in models directly by `require(.../model)`, we now go via the
* database instead.
*
* model = require(.../db).model('Model');
*/
var modelCache = {};
db.model = function(name) {
// `normalizedName` is a conversion from 'some name' to 'someName'
var normalized = name.replace(/(^| +)([a-z])/ig, function(match, space, character) {
return character[space ? 'toUpperCase' : 'toLowerCase']();
});
// `name` is a conversion from 'some name' into 'SomeName'
var name = normalized.replace(/(^| +)([a-z])/ig, function(match, space, character) {
return character.toUpperCase();
});
// `key` is a conversion from 'some name' to 'somename'
var key = name.toLowerCase();
if (!modelCache[key]) {
console.log('Defining model:', name);
var definition = require(path.join(MODEL_PATH, normalized)),
properties = definition.properties,
relationships = definition.relationships,
setup = definition.setup;
delete definition.properties;
delete definition.relationships;
delete definition.setup;
var model = db.define(name, properties, definition);
// We need to cache the model before resolving any relationships, so that it
// is available to any related models that might reference it.
modelCache[key] = model;
if (relationships) {
relationships.forEach(function(relationship) {
var relatedModel = db.model(relationship.model),
type = relationship.type;
console.log('Establishing relationship:', name + '.' + type + '(' + relatedModel.name + ')');
delete relationship.model;
delete relationship.type;
model[type](relatedModel, relationship);
});
}
if (typeof setup === 'function')
setup(model);
}
return modelCache[key];
}
db.type = Sequelize;
db.healthCheck = function(meta, cb) {
var conn = require('mysql').createConnection({
host: DB_HOST,
port: DB_PORT,
database: DB_NAME,
user: USERNAME,
password: PASSWORD,
});
meta.notes = 'mysql://' + USERNAME + "@" + (DB_HOST || "localhost") +
':' + (DB_PORT || '3306') + '/' + DB_NAME;
conn.connect();
conn.query('SHOW TABLES', cb);
conn.end();
};
const ALLOWED_ERROR_TYPES = [
'ER_DUP_FIELDNAME',
'ER_DUP_KEYNAME'
];
db.runMigrations = function (target, migrations, callback) {
async.mapSeries(migrations, function (migration, callback) {
target[migration.type]
.apply(target, migration.args)
.complete(callback);
}, function (err) {
if (err && ALLOWED_ERROR_TYPES.indexOf(err.code) < 0)
// There are some errors that happen because the database is
// more up-to-date than migrations know, so we ignore them
return callback(err);
callback();
});
}
module.exports = db;