mailtrain/server/lib/dbcheck.js

232 lines
6.8 KiB
JavaScript
Raw Normal View History

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
*/
2018-09-27 19:32:35 +00:00
const config = require('config');
const mysql = require('mysql');
2018-09-27 19:32:35 +00:00
const log = require('./log');
const fs = require('fs');
const pathlib = require('path');
const Handlebars = require('handlebars');
const bluebird = require('bluebird');
const highestLegacySchemaVersion = 33;
2016-04-19 09:05:13 +00:00
2018-09-27 19:32:35 +00:00
const mysqlConfig = {
multipleStatements: true
};
Object.keys(config.mysql).forEach(key => mysqlConfig[key] = config.mysql[key]);
2018-09-27 19:32:35 +00:00
const 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') {
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') {
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) {
db.getConnection((err, connection) => {
2016-04-19 09:05:13 +00:00
if (err) {
return callback(err);
}
connection.query('SHOW TABLES LIKE "knex_migrations"', (err, rows) => {
if (err) {
return callback(err);
}
if (rows.length > 0) {
connection.release();
callback(null, highestLegacySchemaVersion);
} else {
connection.query('SELECT `value` FROM `settings` WHERE `key`=?', ['db_schema_version'], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
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;
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),
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;
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);
}
function applyUpdate(update, callback) {
getSql(update.path, update.data, (err, sql) => {
if (err) {
return callback(err);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query(sql, err => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, true);
});
});
});
}
function runUpdates(runCount, callback) {
2016-04-19 09:05:13 +00:00
listTables((err, tables) => {
if (err) {
return callback(err);
}
if (!tables.settings) {
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');
return runInitial(runUpdates.bind(null, ++runCount, callback));
2016-04-19 09:05:13 +00:00
}
getSchemaVersion((err, schemaVersion) => {
if (err) {
return callback(err);
}
if (schemaVersion >= highestLegacySchemaVersion) {
// 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();
});
});
});
}
const runUpdatesAsync = bluebird.promisify(runUpdates);
const dbEndAsync = bluebird.promisify(db.end.bind(db));
2016-04-19 09:05:13 +00:00
async function dbcheck() {
await runUpdatesAsync(0);
await dbEndAsync();
log.info('sql', 'Database check completed');
2016-04-19 09:05:13 +00:00
}
module.exports = dbcheck;