2016-04-19 09:05:13 +00:00
|
|
|
'use strict';
|
|
|
|
|
2016-04-25 12:39:17 +00:00
|
|
|
/*
|
|
|
|
This module handles Mailtrain database initialization and upgrades
|
|
|
|
*/
|
|
|
|
|
2016-04-19 09:05:13 +00:00
|
|
|
let config = require('config');
|
2016-04-24 19:40:01 +00:00
|
|
|
let mysql = require('mysql');
|
2016-04-19 09:05:13 +00:00
|
|
|
let log = require('npmlog');
|
|
|
|
let fs = require('fs');
|
|
|
|
let pathlib = require('path');
|
|
|
|
let Handlebars = require('handlebars');
|
2016-04-25 10:59:00 +00:00
|
|
|
let meta = require('../meta.json');
|
2016-04-19 09:05:13 +00:00
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
let mysqlConfig = {
|
|
|
|
multipleStatements: true
|
|
|
|
};
|
|
|
|
Object.keys(config.mysql).forEach(key => mysqlConfig[key] = config.mysql[key]);
|
|
|
|
let db = mysql.createPool(mysqlConfig);
|
2016-04-19 09:05:13 +00:00
|
|
|
|
|
|
|
function listTables(callback) {
|
|
|
|
db.getConnection((err, connection) => {
|
|
|
|
if (err) {
|
2016-04-29 11:57:13 +00:00
|
|
|
if (err.code === 'ER_ACCESS_DENIED_ERROR') {
|
2016-04-26 12:46:29 +00:00
|
|
|
err = new Error('Could not access the database. Check MySQL config and authentication credentials');
|
|
|
|
}
|
2016-04-29 11:57:13 +00:00
|
|
|
if (err.code === 'ECONNREFUSED' || err.code === 'PROTOCOL_SEQUENCE_TIMEOUT') {
|
2016-04-26 12:46:29 +00:00
|
|
|
err = new Error('Could not connect to the database. Check MySQL host and port configuration');
|
|
|
|
}
|
2016-04-19 09:05:13 +00:00
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
let query = 'SHOW TABLES';
|
|
|
|
connection.query(query, (err, rows) => {
|
|
|
|
connection.release();
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
let tables = {};
|
|
|
|
[].concat(rows || []).forEach(row => {
|
|
|
|
let name;
|
|
|
|
let table;
|
|
|
|
Object.keys(row).forEach(key => {
|
|
|
|
if (/^Tables_in_/i.test(key)) {
|
|
|
|
table = name = row[key];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
if (/__\d+$/.test(name)) {
|
|
|
|
let parts = name.split('__');
|
|
|
|
parts.pop();
|
|
|
|
table = parts.join('__');
|
|
|
|
}
|
|
|
|
if (tables.hasOwnProperty(table)) {
|
|
|
|
tables[table].push(name);
|
|
|
|
} else {
|
|
|
|
tables[table] = [name];
|
|
|
|
}
|
|
|
|
return table;
|
|
|
|
});
|
|
|
|
return callback(null, tables);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSchemaVersion(callback) {
|
2016-04-25 10:59:00 +00:00
|
|
|
db.getConnection((err, connection) => {
|
2016-04-19 09:05:13 +00:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-04-25 10:59:00 +00:00
|
|
|
connection.query('SELECT `value` FROM `settings` WHERE `key`=?', ['db_schema_version'], (err, rows) => {
|
|
|
|
connection.release();
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-04-19 09:05:13 +00:00
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
let dbSchemaVersion = rows && rows[0] && Number(rows[0].value) || 0;
|
|
|
|
callback(null, dbSchemaVersion);
|
|
|
|
});
|
2016-04-19 09:05:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function listUpdates(current, callback) {
|
|
|
|
current = current || 0;
|
2016-04-25 10:59:00 +00:00
|
|
|
fs.readdir(pathlib.join(__dirname, '..', 'setup', 'sql'), (err, list) => {
|
2016-04-19 09:05:13 +00:00
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
let updates = [];
|
|
|
|
[].concat(list || []).forEach(row => {
|
2016-04-20 17:17:53 +00:00
|
|
|
if (/^upgrade-\d+\.sql$/i.test(row)) {
|
2016-04-19 09:05:13 +00:00
|
|
|
let seq = row.match(/\d+/)[0];
|
|
|
|
if (seq > current) {
|
|
|
|
updates.push({
|
|
|
|
seq: Number(seq),
|
2016-04-25 10:59:00 +00:00
|
|
|
path: pathlib.join(__dirname, '..', 'setup', 'sql', row)
|
2016-04-19 09:05:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return callback(null, updates);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSql(path, data, callback) {
|
|
|
|
fs.readFile(path, 'utf-8', (err, source) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2017-05-09 23:40:02 +00:00
|
|
|
const rendered = data ? Handlebars.compile(source)(data) : source;
|
|
|
|
return callback(null, rendered);
|
2016-04-19 09:05:13 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runInitial(callback) {
|
2017-05-09 23:40:02 +00:00
|
|
|
let dump = process.env.NODE_ENV === 'test' ? 'mailtrain-test.sql' : 'mailtrain.sql';
|
|
|
|
let fname = process.env.DB_FROM_START ? 'base.sql' : dump;
|
2016-04-25 10:59:00 +00:00
|
|
|
let path = pathlib.join(__dirname, '..', 'setup', 'sql', fname);
|
|
|
|
log.info('sql', 'Loading tables from %s', fname);
|
2016-04-19 09:05:13 +00:00
|
|
|
applyUpdate({
|
|
|
|
path
|
|
|
|
}, callback);
|
|
|
|
}
|
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
function runUpdates(callback, runCount) {
|
|
|
|
runCount = Number(runCount) || 0;
|
2016-04-19 09:05:13 +00:00
|
|
|
listTables((err, tables) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tables.settings) {
|
2016-04-25 10:59:00 +00:00
|
|
|
if (runCount) {
|
|
|
|
return callback(new Error('Settings table not found from database'));
|
|
|
|
}
|
2016-04-20 17:17:53 +00:00
|
|
|
log.info('sql', 'SQL not set up, initializing');
|
2016-04-25 10:59:00 +00:00
|
|
|
return runInitial(runUpdates.bind(null, callback, ++runCount));
|
2016-04-19 09:05:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getSchemaVersion((err, schemaVersion) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
if (schemaVersion >= meta.schemaVersion) {
|
|
|
|
// nothing to do here, already updated
|
|
|
|
return callback(null, false);
|
|
|
|
}
|
|
|
|
|
2016-04-19 09:05:13 +00:00
|
|
|
listUpdates(schemaVersion, (err, updates) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
let pos = 0;
|
|
|
|
let runNext = () => {
|
|
|
|
if (pos >= updates.length) {
|
|
|
|
return callback(null, pos);
|
|
|
|
}
|
|
|
|
let update = updates[pos++];
|
|
|
|
update.data = {
|
|
|
|
tables
|
|
|
|
};
|
|
|
|
applyUpdate(update, (err, status) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
if (status) {
|
2016-04-20 17:17:53 +00:00
|
|
|
log.info('sql', 'Update %s applied', update.seq);
|
2016-04-19 09:05:13 +00:00
|
|
|
} else {
|
2016-04-20 17:17:53 +00:00
|
|
|
log.info('sql', 'Update %s not applied', update.seq);
|
2016-04-19 09:05:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
runNext();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
runNext();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function applyUpdate(update, callback) {
|
|
|
|
getSql(update.path, update.data, (err, sql) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
db.getConnection((err, connection) => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-04-19 09:05:13 +00:00
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
connection.query(sql, err => {
|
|
|
|
connection.release();
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-04-19 09:05:13 +00:00
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
return callback(null, true);
|
|
|
|
});
|
2016-04-19 09:05:13 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-04-25 10:59:00 +00:00
|
|
|
module.exports = callback => {
|
|
|
|
runUpdates(err => {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
db.end(() => {
|
|
|
|
log.info('sql', 'Database check completed');
|
|
|
|
return callback(null, true);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|