2013-06-06 19:45:36 +04:00
|
|
|
const async = require('async');
|
2013-04-18 15:10:15 +04:00
|
|
|
const path = require('path');
|
|
|
|
|
2013-04-08 20:57:55 +04:00
|
|
|
const Sequelize = require('sequelize');
|
|
|
|
|
2013-04-08 21:24:18 +04:00
|
|
|
const DB_NAME = process.env['CSOL_DB_NAME'] || 'csol';
|
2013-04-08 20:57:55 +04:00
|
|
|
const USERNAME = process.env['CSOL_DB_USER'] || 'root';
|
|
|
|
const PASSWORD = process.env['CSOL_DB_PASS'];
|
2013-05-01 13:40:06 +04:00
|
|
|
const DB_HOST = process.env['CSOL_DB_HOST'];
|
|
|
|
const DB_PORT = process.env['CSOL_DB_PORT'];
|
2013-04-18 15:10:15 +04:00
|
|
|
const MODEL_PATH = process.env['CSOL_MODEL_PATH'] || path.join(__dirname, 'models');
|
2013-04-08 20:57:55 +04:00
|
|
|
|
2013-04-08 22:29:06 +04:00
|
|
|
const db = new Sequelize(DB_NAME, USERNAME, PASSWORD, {
|
2013-05-01 13:40:06 +04:00
|
|
|
host: DB_HOST,
|
|
|
|
port: DB_PORT,
|
2013-04-08 20:57:55 +04:00
|
|
|
define: { charset: 'utf8' }
|
|
|
|
});
|
|
|
|
|
2013-04-19 11:56:36 +04:00
|
|
|
/**
|
|
|
|
* In order to get useful bi-directional relationships on models, the relationship
|
|
|
|
* has to be defined on both sides:
|
|
|
|
*
|
2013-04-19 16:17:19 +04:00
|
|
|
* Learner.belongsTo(Guardian);
|
2013-04-19 11:56:36 +04:00
|
|
|
* Guardian.hasMany(Learner);
|
|
|
|
*
|
|
|
|
* This automatically adds functionality to both parties, and without both
|
|
|
|
* relationships being defined, half would be missing:
|
|
|
|
*
|
2013-04-19 16:31:55 +04:00
|
|
|
* <learner instance>.[get|set]Guardian();
|
|
|
|
* <guardian instance>.[get|set]Learners();
|
|
|
|
* <guardian instance>.[add|remove]Learner();
|
2013-04-19 11:56:36 +04:00
|
|
|
*
|
|
|
|
* 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');
|
|
|
|
*/
|
|
|
|
|
2013-04-18 14:48:12 +04:00
|
|
|
var modelCache = {};
|
|
|
|
|
|
|
|
db.model = function(name) {
|
2013-04-19 11:58:27 +04:00
|
|
|
// `normalizedName` is a conversion from 'some name' to 'someName'
|
2013-04-18 14:48:12 +04:00
|
|
|
var normalized = name.replace(/(^| +)([a-z])/ig, function(match, space, character) {
|
|
|
|
return character[space ? 'toUpperCase' : 'toLowerCase']();
|
2013-04-19 11:58:27 +04:00
|
|
|
});
|
|
|
|
|
|
|
|
// `name` is a conversion from 'some name' into 'SomeName'
|
|
|
|
var name = normalized.replace(/(^| +)([a-z])/ig, function(match, space, character) {
|
2013-04-18 14:48:12 +04:00
|
|
|
return character.toUpperCase();
|
2013-04-19 11:58:27 +04:00
|
|
|
});
|
|
|
|
|
|
|
|
// `key` is a conversion from 'some name' to 'somename'
|
|
|
|
var key = name.toLowerCase();
|
2013-04-18 14:48:12 +04:00
|
|
|
|
|
|
|
if (!modelCache[key]) {
|
|
|
|
console.log('Defining model:', name);
|
|
|
|
|
2013-04-18 15:10:15 +04:00
|
|
|
var definition = require(path.join(MODEL_PATH, normalized)),
|
2013-04-18 14:48:12 +04:00
|
|
|
properties = definition.properties,
|
2013-05-28 21:18:23 +04:00
|
|
|
relationships = definition.relationships,
|
|
|
|
setup = definition.setup;
|
2013-04-18 14:48:12 +04:00
|
|
|
|
|
|
|
delete definition.properties;
|
|
|
|
delete definition.relationships;
|
2013-05-28 21:18:23 +04:00
|
|
|
delete definition.setup;
|
2013-04-18 14:48:12 +04:00
|
|
|
|
|
|
|
var model = db.define(name, properties, definition);
|
2013-04-19 11:58:27 +04:00
|
|
|
// We need to cache the model before resolving any relationships, so that it
|
|
|
|
// is available to any related models that might reference it.
|
2013-04-18 14:48:12 +04:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
2013-05-28 21:18:23 +04:00
|
|
|
|
|
|
|
if (typeof setup === 'function')
|
|
|
|
setup(model);
|
2013-04-18 14:48:12 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
return modelCache[key];
|
|
|
|
}
|
|
|
|
|
2013-04-08 20:57:55 +04:00
|
|
|
db.type = Sequelize;
|
2013-06-06 19:45:36 +04:00
|
|
|
|
2013-06-04 06:18:28 +04:00
|
|
|
db.healthCheck = function(meta, cb) {
|
2013-06-04 04:41:10 +04:00
|
|
|
var conn = require('mysql').createConnection({
|
|
|
|
host: DB_HOST,
|
|
|
|
port: DB_PORT,
|
|
|
|
database: DB_NAME,
|
|
|
|
user: USERNAME,
|
|
|
|
password: PASSWORD,
|
2013-06-04 00:30:31 +04:00
|
|
|
});
|
2013-06-04 06:18:28 +04:00
|
|
|
meta.notes = 'mysql://' + USERNAME + "@" + (DB_HOST || "localhost") +
|
|
|
|
':' + (DB_PORT || '3306') + '/' + DB_NAME;
|
2013-06-04 04:41:10 +04:00
|
|
|
conn.connect();
|
|
|
|
conn.query('SHOW TABLES', cb);
|
|
|
|
conn.end();
|
2013-06-04 00:30:31 +04:00
|
|
|
};
|
2013-06-06 19:45:36 +04:00
|
|
|
|
|
|
|
db.runMigrations = function (migrations, callback) {
|
|
|
|
async.mapSeries(migrations, function (migration, callback) {
|
|
|
|
migration.complete(callback);
|
|
|
|
}, function (err) {
|
|
|
|
if (err && err.code !== 'ER_DUP_FIELDNAME')
|
|
|
|
// If the error is 'ER_DUP_FIELDNAME', we're going to assume it's
|
|
|
|
// because the database is more up-to-date than migrations know.
|
|
|
|
return callback(err);
|
|
|
|
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-04-08 20:57:55 +04:00
|
|
|
module.exports = db;
|