Fixes in reports (generating a CSV).

Added caching of generated images in mosaico handler.
Various other fixes.
This commit is contained in:
Tomas Bures 2019-04-22 02:41:40 +02:00
parent 055c4c6b51
commit 66702b5edc
39 changed files with 545 additions and 278 deletions

View file

@ -6,6 +6,7 @@ const net = require('net');
const campaigns = require('../models/campaigns');
const contextHelpers = require('../lib/context-helpers');
const { SubscriptionStatus } = require('../../shared/lists');
const bluebird = require('bluebird');
const seenIds = new Set();
@ -33,7 +34,7 @@ async function readNextChunks() {
try {
const match = /\bstatus=(bounced|sent)\b/.test(line) && line.match(/\bpostfix\/\w+\[\d+\]:\s*([^:]+).*?status=(\w+)/);
if (match) {
let queueId = match[1];
const queueId = match[1];
let queued = '';
let queuedAs = '';
@ -41,7 +42,7 @@ async function readNextChunks() {
seenIds.add(queueId);
// Losacno: Check for local requeue
let status = match[2];
const status = match[2];
log.verbose('POSTFIXBOUNCE', 'Checking message %s for local requeue (status: %s)', queueId, status);
if (status === 'sent') {
// Save new queueId to update message's previous queueId (thanks @mfechner )
@ -82,7 +83,7 @@ async function readNextChunks() {
}
}
module.exports = callback => {
function spawn(callback) {
if (!config.postfixbounce.enabled) {
return setImmediate(callback);
}
@ -122,4 +123,7 @@ module.exports = callback => {
log.info('POSTFIXBOUNCE', 'Server listening on port %s', config.postfixbounce.port);
setImmediate(callback);
});
};
}
module.exports.spawn = bluebird.promisify(spawn);

View file

@ -5,6 +5,7 @@ const config = require('config');
const crypto = require('crypto');
const humanize = require('humanize');
const http = require('http');
const bluebird = require('bluebird');
const SMTPServer = require('smtp-server').SMTPServer;
const simpleParser = require('mailparser').simpleParser;
@ -22,7 +23,7 @@ const mailstore = {
},
getMail(address, callback) {
if (!this.accounts[address] || this.accounts[address].length === 0) {
let err = new Error('No mail for ' + address);
const err = new Error('No mail for ' + address);
err.status = 404;
return callback(err);
}
@ -55,8 +56,8 @@ const server = new SMTPServer({
// Setup authentication
onAuth: (auth, session, callback) => {
let username = config.testServer.username;
let password = config.testServer.password;
const username = config.testServer.username;
const password = config.testServer.password;
// check username and password
if (auth.username === username && auth.password === password) {
@ -80,15 +81,13 @@ const server = new SMTPServer({
// Validate RCPT TO envelope address. Example allows all addresses that do not start with 'deny'
// If this method is not set, all addresses are allowed
onRcptTo: (address, session, callback) => {
let err;
if (/^deny/i.test(address.address)) {
return callback(new Error('Not accepted'));
}
// Reject messages larger than 100 bytes to an over-quota user
if (/^full/i.test(address.address) && Number(session.envelope.mailFrom.args.SIZE) > 100) {
err = new Error('Insufficient channel storage: ' + address.address);
const err = new Error('Insufficient channel storage: ' + address.address);
err.responseCode = 452;
return callback(err);
}
@ -98,7 +97,7 @@ const server = new SMTPServer({
// Handle message stream
onData: (stream, session, callback) => {
let hash = crypto.createHash('md5');
const hash = crypto.createHash('md5');
let message = '';
stream.on('data', chunk => {
hash.update(chunk);
@ -107,9 +106,8 @@ const server = new SMTPServer({
}
});
stream.on('end', () => {
let err;
if (stream.sizeExceeded) {
err = new Error('Error: message exceeds fixed maximum message size 10 MB');
const err = new Error('Error: message exceeds fixed maximum message size 10 MB');
err.responseCode = 552;
return callback(err);
}
@ -129,15 +127,15 @@ server.on('error', err => {
log.error('Test SMTP', err.stack);
});
let mailBoxServer = http.createServer((req, res) => {
let renderer = data => (
const mailBoxServer = http.createServer((req, res) => {
const renderer = data => (
'<!doctype html><html><head><title>' + data.title + '</title></head><body>' + data.body + '</body></html>'
);
let address = req.url.substring(1);
const address = req.url.substring(1);
mailstore.getMail(address, (err, mail) => {
if (err) {
let html = renderer({
const html = renderer({
title: 'error',
body: err.message || err
});
@ -155,7 +153,7 @@ let mailBoxServer = http.createServer((req, res) => {
delete mail.textAsHtml;
delete mail.attachments;
let script = '<script> var mailObject = ' + JSON.stringify(mail) + '; console.log(mailObject); </script>';
const script = '<script> var mailObject = ' + JSON.stringify(mail) + '; console.log(mailObject); </script>';
html = html.replace(/<\/body\b/i, match => script + match);
html = html.replace(/target="_blank"/g, 'target="_self"');
@ -168,7 +166,7 @@ mailBoxServer.on('error', err => {
log.error('Test SMTP Mailbox Server', err);
});
module.exports = callback => {
function spawn(callback) {
if (config.testServer.enabled) {
server.listen(config.testServer.port, config.testServer.host, () => {
log.info('Test SMTP', 'Server listening on port %s', config.testServer.port);
@ -194,4 +192,6 @@ module.exports = callback => {
} else {
setImmediate(callback);
}
};
}
module.exports.spawn = bluebird.promisify(spawn);

View file

@ -7,6 +7,7 @@ const {MailerError} = require('../lib/mailers');
const campaigns = require('../models/campaigns');
const contextHelpers = require('../lib/context-helpers');
const {SubscriptionStatus} = require('../../shared/lists');
const bluebird = require('bluebird');
const BounceHandler = require('bounce-handler').BounceHandler;
const SMTPServer = require('smtp-server').SMTPServer;
@ -85,7 +86,7 @@ const server = new SMTPServer({
onData: onData
});
module.exports = callback => {
function spawn(callback) {
if (!config.verp.enabled) {
return setImmediate(callback);
}
@ -131,7 +132,7 @@ module.exports = callback => {
started = true;
return setImmediate(callback);
}
let host = hosts[pos++];
const host = hosts[pos++];
server.listen(config.verp.port, host, () => {
if (started) {
return server.close();
@ -142,4 +143,6 @@ module.exports = callback => {
};
startNextHost();
};
}
module.exports.spawn = bluebird.promisify(spawn);

View file

@ -1,9 +1,9 @@
'use strict';
const reports = require('../../../models/reports');
const reportTemplates = require('../../../models/report-templates');
const lists = require('../../../models/lists');
const subscriptions = require('../../../models/subscriptions');
const { SubscriptionSource, SubscriptionStatus } = require('../../../../shared/lists');
const campaigns = require('../../../models/campaigns');
const handlebars = require('handlebars');
const hbs = require('hbs');
@ -50,9 +50,11 @@ async function main() {
}
const campaignsProxy = {
getCampaignStatistics: reports.getCampaignStatistics,
getCampaignOpenStatistics: reports.getCampaignOpenStatistics,
getCampaignClickStatistics: reports.getCampaignClickStatistics,
getCampaignLinkClickStatistics: reports.getCampaignLinkClickStatistics,
getCampaignStatisticsStream: reports.getCampaignStatisticsStream,
getCampaignOpenStatisticsStream: reports.getCampaignOpenStatisticsStream,
getCampaignClickStatisticsStream: reports.getCampaignClickStatisticsStream,
getCampaignLinkClickStatisticsStream: reports.getCampaignLinkClickStatisticsStream,
@ -71,17 +73,45 @@ async function main() {
knex,
process,
inputs,
SubscriptionSource,
SubscriptionStatus,
renderCsvFromStream: async (readable, opts) => {
const stringifier = csvStringify(opts);
renderCsvFromStream: async (readable, opts, transform) => {
const finished = new Promise((success, fail) => {
stringifier.on('finish', () => success())
stringifier.on('error', (err) => fail(err))
});
let lastReadable = readable;
stringifier.pipe(process.stdout);
readable.pipe(stringifier);
const stringifier = csvStringify(opts);
stringifier.on('finish', () => success());
stringifier.on('error', err => fail(err));
if (transform) {
const rowTransform = new stream.Transform({
objectMode: true,
transform(row, encoding, callback) {
async function performTransform() {
try {
const newRow = await transform(row, encoding);
callback(null, newRow);
} catch (err) {
callback(err);
}
}
// noinspection JSIgnoredPromiseFromCall
performTransform();
}
});
lastReadable.on('error', err => fail(err));
lastReadable.pipe(rowTransform);
lastReadable = rowTransform;
}
stringifier.pipe(process.stdout);
lastReadable.pipe(stringifier);
});
await finished;
},