2022-10-24 16:11:25 +00:00
|
|
|
require('require-yaml');
|
|
|
|
require('colors');
|
2022-10-23 19:46:07 +00:00
|
|
|
const fs = require('fs');
|
|
|
|
const path = require('path');
|
|
|
|
const mysql = require('mysql2/promise');
|
|
|
|
const amqp = require('amqplib');
|
2022-10-25 11:20:22 +00:00
|
|
|
const {cpus} = require('os');
|
|
|
|
|
2022-10-23 19:46:07 +00:00
|
|
|
|
|
|
|
class Consumer {
|
|
|
|
async start() {
|
2022-10-24 16:11:25 +00:00
|
|
|
const defaultConfig = require('./config.yml');
|
|
|
|
const config = this.config = Object.assign({}, defaultConfig);
|
|
|
|
const localPath = path.join(__dirname, 'config.local.yml');
|
|
|
|
if (fs.existsSync(localPath)) {
|
|
|
|
const localConfig = require(localPath);
|
|
|
|
Object.assign(config, localConfig);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config.testMode)
|
2022-10-23 19:46:07 +00:00
|
|
|
console.log('Test mode enabled, just logging queries to console.');
|
|
|
|
|
|
|
|
console.log('Starting process.');
|
|
|
|
await this.init();
|
|
|
|
console.log('Process started.');
|
2022-10-25 11:20:22 +00:00
|
|
|
|
|
|
|
await this.consumeQueues();
|
2022-10-23 19:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async stop() {
|
|
|
|
console.log('Stopping process.');
|
|
|
|
await this.end();
|
|
|
|
console.log('Process stopped.');
|
|
|
|
}
|
|
|
|
|
|
|
|
async init() {
|
2022-10-24 16:11:25 +00:00
|
|
|
const config = this.config;
|
2022-10-23 19:46:07 +00:00
|
|
|
this.onErrorListener = err => this.onError(err);
|
|
|
|
|
2022-10-25 11:20:22 +00:00
|
|
|
const dbConfig = Object.assign({
|
|
|
|
connectionLimit: cpus().length
|
|
|
|
}, config.consumerDb);
|
2022-10-23 19:46:07 +00:00
|
|
|
|
2022-10-25 11:20:22 +00:00
|
|
|
this.db = await mysql.createPool(dbConfig);
|
|
|
|
this.db.on('error', this.onErrorListener);
|
2022-10-23 19:46:07 +00:00
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
this.consumer = await amqp.connect(config.amqp);
|
2022-10-23 19:46:07 +00:00
|
|
|
this.channel = await this.consumer.createChannel();
|
2022-10-25 11:20:22 +00:00
|
|
|
this.channel.prefetch(1);
|
|
|
|
}
|
2022-10-24 16:11:25 +00:00
|
|
|
|
2022-10-25 11:20:22 +00:00
|
|
|
async consumeQueues() {
|
|
|
|
for (const queueName in this.config.queues) {
|
2022-10-24 16:11:25 +00:00
|
|
|
await this.channel.assertQueue(queueName, {
|
|
|
|
durable: true
|
|
|
|
});
|
|
|
|
await this.channel.consume(queueName,
|
|
|
|
msg => this.onConsume(msg, queueName));
|
|
|
|
}
|
2022-10-23 19:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async end(silent) {
|
|
|
|
await this.consumer.close();
|
|
|
|
|
|
|
|
this.db.off('error', this.onErrorListener);
|
|
|
|
// FIXME: mysql2/promise bug, db.end() ends process
|
|
|
|
this.db.on('error', () => {});
|
|
|
|
try {
|
|
|
|
await this.db.end();
|
|
|
|
} catch (err) {
|
|
|
|
if (!silent)
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
async onConsume(msg, queueName) {
|
|
|
|
const config = this.config;
|
|
|
|
const data = JSON.parse(msg.content.toString());
|
2022-10-25 11:20:22 +00:00
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
if (config.debug)
|
2022-10-25 11:20:22 +00:00
|
|
|
console.debug('Message:'.blue, queueName.yellow, data.table);
|
2022-10-23 19:46:07 +00:00
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
const queue = config.queues[queueName];
|
2022-10-25 11:20:22 +00:00
|
|
|
let query = queue.query;
|
2022-10-24 16:11:25 +00:00
|
|
|
if (!query) return;
|
|
|
|
|
2022-10-25 11:20:22 +00:00
|
|
|
// XXX: Testing
|
|
|
|
//query = 'SELECT 1 sleep';
|
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
switch(queue.mode) {
|
|
|
|
case 'fk':
|
2022-10-25 11:20:22 +00:00
|
|
|
for (const fk of data.fks) {
|
|
|
|
const sql = this.db.format(query, fk);
|
|
|
|
this.debug('SQL', sql);
|
|
|
|
if (!config.testMode)
|
2022-10-24 16:11:25 +00:00
|
|
|
await this.db.query(query, fk);
|
2022-10-25 11:20:22 +00:00
|
|
|
}
|
2022-10-24 16:11:25 +00:00
|
|
|
break;
|
|
|
|
case 'changes':
|
2022-10-25 11:20:22 +00:00
|
|
|
const queueTable = queue.includeSchema[data.schema][data.table];
|
|
|
|
for (const row of data.rows) {
|
|
|
|
const sql = this.db.format(query, [
|
|
|
|
data.table,
|
|
|
|
row[queueTable.key],
|
|
|
|
JSON.stringify(row)
|
|
|
|
]);
|
|
|
|
this.debug('SQL', sql);
|
|
|
|
if (!config.testMode)
|
|
|
|
await this.db.query(query, row);
|
|
|
|
}
|
2022-10-24 16:11:25 +00:00
|
|
|
break;
|
2022-10-23 19:46:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-24 16:11:25 +00:00
|
|
|
await this.channel.ack(msg);
|
2022-10-23 19:46:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async tryRestart() {
|
|
|
|
try {
|
|
|
|
await this.init();
|
|
|
|
console.log('Process restarted.');
|
|
|
|
} catch(err) {
|
|
|
|
setTimeout(() => this.tryRestart(), 30);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async onError(err) {
|
|
|
|
console.log(`Error: ${err.code}: ${err.message}`);
|
|
|
|
try {
|
|
|
|
await this.end(true);
|
|
|
|
} catch(e) {}
|
|
|
|
|
|
|
|
switch (err.code) {
|
|
|
|
case 'PROTOCOL_CONNECTION_LOST':
|
|
|
|
case 'ECONNRESET':
|
|
|
|
console.log('Trying to restart process.');
|
|
|
|
await this.tryRestart();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
process.exit();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
debug(namespace, message) {
|
|
|
|
if (this.config.debug)
|
|
|
|
console.debug(`${namespace}:`.blue, message.yellow);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function main() {
|
2022-10-24 16:11:25 +00:00
|
|
|
const consumer = new Consumer()
|
2022-10-23 19:46:07 +00:00
|
|
|
await consumer.start();
|
|
|
|
|
|
|
|
process.on('SIGINT', async function() {
|
|
|
|
console.log('Got SIGINT.');
|
|
|
|
try {
|
|
|
|
await consumer.stop();
|
|
|
|
} catch (err) {
|
|
|
|
console.error(err);
|
|
|
|
}
|
|
|
|
process.exit();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
main();
|