mailtrain/lib/models/campaigns.js

1292 lines
44 KiB
JavaScript
Raw Normal View History

2016-04-04 12:36:30 +00:00
'use strict';
let tools = require('../tools');
let db = require('../db');
let lists = require('./lists');
let templates = require('./templates');
let segments = require('./segments');
2016-04-11 03:26:20 +00:00
let subscriptions = require('./subscriptions');
2016-04-04 12:36:30 +00:00
let shortid = require('shortid');
2016-05-03 09:36:06 +00:00
let isUrl = require('is-url');
let feed = require('../feed');
let log = require('npmlog');
2016-05-04 16:11:41 +00:00
let mailer = require('../mailer');
2016-09-09 19:12:03 +00:00
let humanize = require('humanize');
2017-03-07 14:30:56 +00:00
let _ = require('../translate')._;
2016-04-04 12:36:30 +00:00
2017-03-02 17:52:40 +00:00
let allowedKeys = ['description', 'from', 'address', 'reply_to', 'subject', 'editor_name', 'editor_data', 'template', 'source_url', 'list', 'segment', 'html', 'text', 'tracking_disabled'];
2016-04-04 12:36:30 +00:00
module.exports.list = (start, limit, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT SQL_CALC_FOUND_ROWS * FROM campaigns ORDER BY scheduled DESC LIMIT ? OFFSET ?', [limit, start], (err, rows) => {
2016-04-04 12:36:30 +00:00
if (err) {
connection.release();
return callback(err);
}
connection.query('SELECT FOUND_ROWS() AS total', (err, total) => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, rows, total && total[0] && total[0].total);
});
});
});
};
2016-05-03 09:36:06 +00:00
module.exports.filter = (request, parent, callback) => {
let columns = ['#', 'name', 'description', 'status', 'created'];
let processQuery = queryData => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT COUNT(id) AS total FROM `campaigns`';
let values = [];
if (queryData.where) {
query += ' WHERE ' + queryData.where;
values = values.concat(queryData.values || []);
}
connection.query(query, values, (err, total) => {
if (err) {
connection.release();
return callback(err);
}
total = total && total[0] && total[0].total || 0;
let ordering = [];
if (request.order && request.order.length) {
request.order.forEach(order => {
let orderField = columns[Number(order.column)];
let orderDirection = (order.dir || '').toString().toLowerCase() === 'desc' ? 'DESC' : 'ASC';
if (orderField) {
ordering.push('`' + orderField + '` ' + orderDirection);
}
});
}
if (!ordering.length) {
ordering.push('`created` DESC');
}
let args = [Number(request.length) || 50, Number(request.start) || 0];
let query;
if (request.search && request.search.value) {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `campaigns` WHERE name LIKE ? ' + (queryData.where ? ' AND (' + queryData.where + ')' : '') + ' ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
let searchVal = '%' + request.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
args = [searchVal].concat(queryData.values || []).concat(args);
} else {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `campaigns` WHERE 1 ' + (queryData.where ? ' AND (' + queryData.where + ')' : '') + ' ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
args = [].concat(queryData.values || []).concat(args);
}
connection.query(query, args, (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
connection.query('SELECT FOUND_ROWS() AS total', (err, filteredTotal) => {
connection.release();
if (err) {
return callback(err);
}
let subscriptions = rows.map(row => tools.convertKeys(row));
filteredTotal = filteredTotal && filteredTotal[0] && filteredTotal[0].total || 0;
return callback(null, subscriptions, total, filteredTotal);
});
});
});
});
};
2016-05-03 09:36:06 +00:00
if (parent) {
processQuery({
// only find normal and RSS parent campaigns at this point
where: '`parent`=?',
values: [parent]
});
} else {
processQuery({
// only find normal and RSS parent campaigns at this point
where: '`type` IN (?,?,?)',
values: [1, 2, 4]
2016-05-03 09:36:06 +00:00
});
}
};
2016-05-13 12:32:29 +00:00
module.exports.filterClickedSubscribers = (campaign, linkId, request, columns, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT COUNT(`subscription__' + campaign.list + '`.`id`) AS total FROM `subscription__' + campaign.list + '` JOIN `campaign_tracker__' + campaign.id + '` ON `campaign_tracker__' + campaign.id + '`.`list`=? AND `campaign_tracker__' + campaign.id + '`.`subscriber`=`subscription__' + campaign.list + '`.`id` AND `campaign_tracker__' + campaign.id + '`.`link`=?';
let values = [campaign.list, linkId];
connection.query(query, values, (err, total) => {
if (err) {
connection.release();
return callback(err);
}
total = total && total[0] && total[0].total || 0;
let ordering = [];
if (request.order && request.order.length) {
request.order.forEach(order => {
let orderField = columns[Number(order.column)];
let orderDirection = (order.dir || '').toString().toLowerCase() === 'desc' ? 'DESC' : 'ASC';
if (orderField) {
ordering.push('`' + orderField + '` ' + orderDirection);
}
});
}
if (!ordering.length) {
ordering.push('`email` ASC');
}
let args = [Number(request.length) || 50, Number(request.start) || 0];
if (request.search && request.search.value) {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `subscription__' + campaign.list + '` JOIN `campaign_tracker__' + campaign.id + '` ON `campaign_tracker__' + campaign.id + '`.`list`=? AND `campaign_tracker__' + campaign.id + '`.`subscriber`=`subscription__' + campaign.list + '`.`id` AND `campaign_tracker__' + campaign.id + '`.`link`=? WHERE email LIKE ? OR first_name LIKE ? OR last_name LIKE ? ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
let searchVal = '%' + request.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
args = values.concat([searchVal, searchVal, searchVal]).concat(args);
} else {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `subscription__' + campaign.list + '` JOIN `campaign_tracker__' + campaign.id + '` ON `campaign_tracker__' + campaign.id + '`.`list`=? AND `campaign_tracker__' + campaign.id + '`.`subscriber`=`subscription__' + campaign.list + '`.`id` AND `campaign_tracker__' + campaign.id + '`.`link`=? ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
args = values.concat(args);
}
connection.query(query, args, (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
connection.query('SELECT FOUND_ROWS() AS total', (err, filteredTotal) => {
connection.release();
if (err) {
return callback(err);
}
let subscriptions = rows.map(row => tools.convertKeys(row));
filteredTotal = filteredTotal && filteredTotal[0] && filteredTotal[0].total || 0;
return callback(null, subscriptions, total, filteredTotal);
});
});
});
});
};
module.exports.filterStatusSubscribers = (campaign, status, request, columns, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
status = Number(status) || 0;
let query = 'SELECT COUNT(`subscription__' + campaign.list + '`.`id`) AS total FROM `subscription__' + campaign.list + '` JOIN `campaign__' + campaign.id + '` ON `campaign__' + campaign.id + '`.`list`=? AND `campaign__' + campaign.id + '`.`segment`=? AND `campaign__' + campaign.id + '`.`subscription`=`subscription__' + campaign.list + '`.`id` WHERE `campaign__' + campaign.id + '`.`status`=?';
2016-11-13 11:50:51 +00:00
let values = [campaign.list, campaign.segment && campaign.segment.id || 0, status];
connection.query(query, values, (err, total) => {
if (err) {
2016-11-13 11:50:51 +00:00
connection.release();
return callback(err);
}
total = total && total[0] && total[0].total || 0;
let ordering = [];
if (request.order && request.order.length) {
request.order.forEach(order => {
let orderField = columns[Number(order.column)];
let orderDirection = (order.dir || '').toString().toLowerCase() === 'desc' ? 'DESC' : 'ASC';
if (orderField) {
ordering.push('`' + orderField + '` ' + orderDirection);
}
});
}
if (!ordering.length) {
ordering.push('`email` ASC');
}
let args = [Number(request.length) || 50, Number(request.start) || 0];
if (request.search && request.search.value) {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `subscription__' + campaign.list + '` JOIN `campaign__' + campaign.id + '` ON `campaign__' + campaign.id + '`.`list`=? AND `campaign__' + campaign.id + '`.`segment`=? AND `campaign__' + campaign.id + '`.`subscription`=`subscription__' + campaign.list + '`.`id` WHERE `campaign__' + campaign.id + '`.`status`=? AND (email LIKE ? OR first_name LIKE ? OR last_name LIKE ?) ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
let searchVal = '%' + request.search.value.replace(/\\/g, '\\\\').replace(/([%_])/g, '\\$1') + '%';
args = values.concat([searchVal, searchVal, searchVal]).concat(args);
} else {
query = 'SELECT SQL_CALC_FOUND_ROWS * FROM `subscription__' + campaign.list + '` JOIN `campaign__' + campaign.id + '` ON `campaign__' + campaign.id + '`.`list`=? AND `campaign__' + campaign.id + '`.`segment`=? AND `campaign__' + campaign.id + '`.`subscription`=`subscription__' + campaign.list + '`.`id` WHERE `campaign__' + campaign.id + '`.`status`=? ORDER BY ' + ordering.join(', ') + ' LIMIT ? OFFSET ?';
args = values.concat(args);
}
2016-05-13 12:32:29 +00:00
connection.query(query, args, (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
connection.query('SELECT FOUND_ROWS() AS total', (err, filteredTotal) => {
connection.release();
if (err) {
return callback(err);
}
let subscriptions = rows.map(row => tools.convertKeys(row));
filteredTotal = filteredTotal && filteredTotal[0] && filteredTotal[0].total || 0;
return callback(null, subscriptions, total, filteredTotal);
});
});
});
});
};
2016-04-04 12:36:30 +00:00
module.exports.getByCid = (cid, callback) => {
cid = (cid || '').toString().trim();
if (!cid) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
2016-04-04 12:36:30 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT * FROM campaigns WHERE cid=?', [cid], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let campaign = tools.convertKeys(rows[0]);
return callback(null, campaign);
});
});
};
module.exports.get = (id, withSegment, callback) => {
id = Number(id) || 0;
if (id < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
2016-04-04 12:36:30 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT * FROM campaigns WHERE id=?', [id], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let campaign = tools.convertKeys(rows[0]);
let handleSegment = () => {
if (!campaign.segment || !withSegment) {
return callback(null, campaign);
} else {
segments.get(campaign.segment, (err, segment) => {
if (err || !segment) {
// ignore
return callback(null, campaign);
2016-04-04 12:36:30 +00:00
}
segments.subscribers(segment.id, true, (err, subscribers) => {
if (err || !subscribers) {
segment.subscribers = 0;
} else {
segment.subscribers = subscribers;
}
campaign.segment = segment;
return callback(null, campaign);
});
2016-04-04 12:36:30 +00:00
});
}
};
if (!campaign.parent) {
return handleSegment();
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT `id`, `cid`, `name` FROM campaigns WHERE id=?', [campaign.parent], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return handleSegment();
}
campaign.parent = tools.convertKeys(rows[0]);
return handleSegment();
2016-04-04 12:36:30 +00:00
});
});
});
});
};
2016-09-09 19:12:03 +00:00
module.exports.getAttachments = (campaign, callback) => {
campaign = Number(campaign) || 0;
if (campaign < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
2016-09-09 19:12:03 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
2016-09-09 20:09:04 +00:00
let keys = ['id', 'filename', 'content_type', 'size', 'created'];
connection.query('SELECT `' + keys.join('`, `') + '` FROM `attachments` WHERE `campaign`=?', [campaign], (err, rows) => {
2016-09-09 19:12:03 +00:00
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, []);
}
let attachments = rows.map((row, i) => {
row = tools.convertKeys(row);
row.index = i + 1;
row.size = humanize.filesize(Number(row.size) || 0);
return row;
});
return callback(null, attachments);
});
});
};
module.exports.addAttachment = (id, attachment, callback) => {
let size = attachment.content ? attachment.content.length : 0;
if (!size) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Emtpy or too large attahcment')));
2016-09-09 19:12:03 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let keys = ['campaign', 'size'];
let values = [id, size];
Object.keys(attachment).forEach(key => {
let value;
if (Buffer.isBuffer(attachment[key])) {
value = attachment[key];
} else {
value = typeof attachment[key] === 'number' ? attachment[key] : (attachment[key] || '').toString().trim();
}
key = tools.toDbKey(key);
keys.push(key);
values.push(value);
});
let query = 'INSERT INTO `attachments` (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
connection.query(query, values, (err, result) => {
connection.release();
if (err) {
return callback(err);
}
let attachmentId = result && result.insertId || false;
return callback(null, attachmentId);
});
});
};
module.exports.deleteAttachment = (id, attachment, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'DELETE FROM `attachments` WHERE `id`=? AND `campaign`=? LIMIT 1';
connection.query(query, [attachment, id], (err, result) => {
connection.release();
if (err) {
return callback(err);
}
let deleted = result && result.affectedRows || false;
return callback(null, deleted);
});
});
};
2016-09-09 20:09:04 +00:00
module.exports.getAttachment = (campaign, attachment, callback) => {
2016-09-09 19:12:03 +00:00
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
2016-09-09 20:09:04 +00:00
let query = 'SELECT * FROM `attachments` WHERE `id`=? AND `campaign`=? LIMIT 1';
connection.query(query, [attachment, campaign], (err, rows) => {
2016-09-09 19:12:03 +00:00
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let attachment = tools.convertKeys(rows[0]);
return callback(null, attachment);
});
});
};
2016-05-13 12:32:29 +00:00
module.exports.getLinks = (id, linkId, callback) => {
if (!callback && typeof linkId === 'function') {
callback = linkId;
linkId = false;
}
id = Number(id) || 0;
2016-05-13 12:32:29 +00:00
linkId = Number(linkId) || 0;
if (id < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
2016-05-13 12:32:29 +00:00
let query;
let values;
if (!linkId) {
query = 'SELECT `id`, `url`, `clicks` FROM links WHERE `campaign`=? LIMIT 1000';
values = [id];
} else {
query = 'SELECT `id`, `url`, `clicks` FROM links WHERE `id`=? AND `campaign`=? LIMIT 1';
values = [linkId, id];
}
connection.query(query, values, (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, []);
2016-04-04 12:36:30 +00:00
}
let links = rows.map(
row => tools.convertKeys(row)
).sort((a, b) => (
a.url.replace(/^https?:\/\/(www.)?/, '').toLowerCase()).localeCompare(b.url.replace(/^https?:\/\/(www.)?/, '').toLowerCase()));
return callback(null, links);
2016-04-04 12:36:30 +00:00
});
2016-04-04 12:36:30 +00:00
});
};
module.exports.create = (campaign, opts, callback) => {
2016-04-04 12:36:30 +00:00
campaign = tools.convertKeys(campaign);
let name = (campaign.name || '').toString().trim();
2016-09-08 11:39:41 +00:00
campaign.trackingDisabled = campaign.trackingDisabled ? 1 : 0;
opts = opts || {};
2016-04-04 12:36:30 +00:00
if (/^\d+:\d+$/.test(campaign.list)) {
campaign.segment = Number(campaign.list.split(':').pop());
campaign.list = Number(campaign.list.split(':').shift());
} else {
campaign.list = Number(campaign.list) || 0;
campaign.segment = 0;
}
switch ((campaign.type || '').toString().trim().toLowerCase()) {
case 'triggered':
campaign.type = 4;
break;
case 'rss':
campaign.type = 2;
break;
case 'entry':
if (opts.parent) {
campaign.type = 3;
} else {
campaign.type = 1;
}
break;
case 'normal':
default:
campaign.type = 1;
}
2016-04-04 12:36:30 +00:00
campaign.template = Number(campaign.template) || 0;
if (!name) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Campaign Name must be set')));
2016-04-04 12:36:30 +00:00
}
if (campaign.type === 2 && (!campaign.sourceUrl || !isUrl(campaign.sourceUrl))) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('RSS URL must be set and needs to be a valid URL')));
2016-05-03 09:36:06 +00:00
}
let getList = (listId, callback) => {
if (campaign.type === 4) {
return callback(null, false);
}
lists.get(listId, (err, list) => {
if (err) {
return callback(err);
}
return callback(null, list || {
id: listId
});
});
};
getList(campaign.list, err => {
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
2016-05-03 09:36:06 +00:00
let keys = ['name', 'type'];
let values = [name, campaign.type];
2016-04-04 12:36:30 +00:00
2016-05-03 09:36:06 +00:00
if (campaign.type === 2) {
keys.push('status');
values.push(5); // inactive
}
if (campaign.type === 3) {
keys.push('status', 'parent');
values.push(2, opts.parent);
}
if (campaign.type === 4) {
keys.push('status');
values.push(6); // active
}
2016-05-03 09:36:06 +00:00
let create = next => {
2016-04-04 12:36:30 +00:00
Object.keys(campaign).forEach(key => {
let value = typeof campaign[key] === 'number' ? campaign[key] : (campaign[key] || '').toString().trim();
key = tools.toDbKey(key);
if (allowedKeys.indexOf(key) >= 0 && keys.indexOf(key) < 0) {
keys.push(key);
values.push(value);
}
});
let cid = shortid.generate();
keys.push('cid');
values.push(cid);
2016-05-03 16:21:01 +00:00
tools.prepareHtml(campaign.html, (err, preparedHtml) => {
2016-04-04 12:36:30 +00:00
if (err) {
2016-05-03 16:21:01 +00:00
log.error('jsdom', err);
2016-04-04 12:36:30 +00:00
}
2017-01-29 16:23:00 +00:00
if (!preparedHtml) {
preparedHtml = campaign.html;
2016-05-03 16:21:01 +00:00
}
2017-01-29 16:23:00 +00:00
keys.push('html_prepared');
values.push(preparedHtml);
2016-05-03 16:21:01 +00:00
db.getConnection((err, connection) => {
2016-04-04 12:36:30 +00:00
if (err) {
2016-05-03 09:36:06 +00:00
return next(err);
2016-04-04 12:36:30 +00:00
}
2016-05-03 16:21:01 +00:00
let query = 'INSERT INTO campaigns (`' + keys.join('`, `') + '`) VALUES (' + values.map(() => '?').join(',') + ')';
connection.query(query, values, (err, result) => {
connection.release();
2016-04-04 12:36:30 +00:00
if (err) {
2016-05-03 09:36:06 +00:00
return next(err);
2016-04-04 12:36:30 +00:00
}
2016-05-03 16:21:01 +00:00
let campaignId = result && result.insertId || false;
if (!campaignId) {
return next(null, false);
}
// we are going to aqcuire a lot of log info, so we are putting
// sending logs into separate tables
createCampaignTables(campaignId, err => {
if (err) {
// FIXME: rollback
return next(err);
}
return next(null, campaignId);
});
2016-04-04 12:36:30 +00:00
});
});
});
};
if (campaign.type === 2) {
2016-05-03 09:36:06 +00:00
feed.fetch(campaign.sourceUrl, (err, entries) => {
if (err) {
return callback(err);
}
2016-05-04 16:11:41 +00:00
mailer.getTemplate('emails/rss-html.hbs', (err, rendererHtml) => {
2016-05-03 09:36:06 +00:00
if (err) {
return callback(err);
}
2016-05-04 16:11:41 +00:00
campaign.html = rendererHtml();
create((err, campaignId) => {
2016-05-03 09:36:06 +00:00
if (err) {
return callback(err);
}
2016-05-04 16:11:41 +00:00
if (!campaignId || !entries.length) {
return callback(null, campaignId);
}
2016-05-03 09:36:06 +00:00
2016-05-04 16:11:41 +00:00
db.getConnection((err, connection) => {
2016-05-03 09:36:06 +00:00
if (err) {
2016-05-04 16:11:41 +00:00
return callback(err);
2016-05-03 09:36:06 +00:00
}
2016-05-04 16:11:41 +00:00
// store references to already existing feed entries
// this is needed to detect new entries
let query = 'INSERT IGNORE INTO `rss` (`parent`,`guid`,`pubdate`) VALUES ' + entries.map(() => '(?,?,?)').join(',');
values = [];
entries.forEach(entry => {
values.push(campaignId, entry.guid, entry.date);
});
connection.query(query, values, err => {
connection.release();
if (err) {
// too late to report as failed
log.error('RSS', err);
}
return callback(null, campaignId);
});
2016-05-03 09:36:06 +00:00
});
});
});
});
return;
} else if (campaign.template) {
2016-04-04 12:36:30 +00:00
templates.get(campaign.template, (err, template) => {
if (err) {
return callback(err);
}
if (!template) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Selected template not found')));
2016-04-04 12:36:30 +00:00
}
2017-03-02 17:52:40 +00:00
campaign.editorName = template.editorName;
campaign.editorData = template.editorData;
2016-05-03 16:21:01 +00:00
campaign.html = template.html;
campaign.text = template.text;
2016-04-04 12:36:30 +00:00
2016-05-03 09:36:06 +00:00
create(callback);
2016-04-04 12:36:30 +00:00
});
2016-05-03 09:36:06 +00:00
return;
2016-04-04 12:36:30 +00:00
} else {
2016-05-03 09:36:06 +00:00
return create(callback);
2016-04-04 12:36:30 +00:00
}
});
};
module.exports.update = (id, updates, callback) => {
updates = updates || {};
id = Number(id) || 0;
if (id < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
2016-04-04 12:36:30 +00:00
}
let campaign = tools.convertKeys(updates);
let name = (campaign.name || '').toString().trim();
2016-09-08 11:39:41 +00:00
campaign.trackingDisabled = campaign.trackingDisabled ? 1 : 0;
2016-04-04 12:36:30 +00:00
if (!name) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Campaign Name must be set')));
2016-04-04 12:36:30 +00:00
}
if (/^\d+:\d+$/.test(campaign.list)) {
campaign.segment = Number(campaign.list.split(':').pop());
campaign.list = Number(campaign.list.split(':').shift());
} else {
campaign.list = Number(campaign.list) || 0;
campaign.segment = 0;
}
let getList = (listId, callback) => {
lists.get(listId, (err, list) => {
if (err) {
return callback(err);
}
return callback(null, list || {
id: listId
});
});
};
getList(campaign.list, err => {
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
let keys = ['name'];
let values = [name];
Object.keys(campaign).forEach(key => {
let value = typeof campaign[key] === 'number' ? campaign[key] : (campaign[key] || '').toString().trim();
key = tools.toDbKey(key);
if (allowedKeys.indexOf(key) >= 0 && keys.indexOf(key) < 0) {
keys.push(key);
values.push(value);
}
});
2016-05-03 16:21:01 +00:00
tools.prepareHtml(campaign.html, (err, preparedHtml) => {
2016-04-04 12:36:30 +00:00
if (err) {
2016-05-03 16:21:01 +00:00
log.error('jsdom', err);
2016-04-04 12:36:30 +00:00
}
2017-01-29 16:23:00 +00:00
if (!preparedHtml) {
preparedHtml = campaign.html;
2016-05-03 16:21:01 +00:00
}
2016-04-04 12:36:30 +00:00
2017-01-29 16:23:00 +00:00
keys.push('html_prepared');
values.push(preparedHtml);
2016-05-03 16:21:01 +00:00
db.getConnection((err, connection) => {
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
2016-05-03 09:36:06 +00:00
2016-05-03 16:21:01 +00:00
values.push(id);
2016-05-03 09:36:06 +00:00
2016-05-03 16:21:01 +00:00
connection.query('UPDATE campaigns SET ' + keys.map(key => '`' + key + '`=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
2016-05-03 09:36:06 +00:00
if (err) {
connection.release();
return callback(err);
}
2016-05-03 16:21:01 +00:00
let affected = result && result.affectedRows || false;
2016-05-03 09:36:06 +00:00
2016-05-03 16:21:01 +00:00
if (!affected) {
2016-05-03 09:36:06 +00:00
connection.release();
return callback(null, affected);
}
2016-05-03 16:21:01 +00:00
connection.query('SELECT `type`, `source_url` FROM campaigns WHERE id=? LIMIT 1', [id], (err, rows) => {
2016-06-22 12:25:36 +00:00
connection.release();
2016-05-03 09:36:06 +00:00
if (err) {
return callback(err);
}
2016-05-03 16:21:01 +00:00
if (!rows || !rows[0] || rows[0].type !== 2) {
// if not RSS, then nothing to do here
return callback(null, affected);
}
// update seen rss entries to avoid sending old entries to subscribers
feed.fetch(rows[0].source_url, (err, entries) => {
2016-05-03 09:36:06 +00:00
if (err) {
2016-05-03 16:21:01 +00:00
return callback(err);
2016-05-03 09:36:06 +00:00
}
2016-05-03 16:21:01 +00:00
2016-06-22 12:25:36 +00:00
db.getConnection((err, connection) => {
2016-05-03 16:21:01 +00:00
if (err) {
2016-06-22 12:25:36 +00:00
return callback(err);
2016-05-03 16:21:01 +00:00
}
2016-06-22 12:25:36 +00:00
let query = 'INSERT IGNORE INTO `rss` (`parent`,`guid`,`pubdate`) VALUES ' + entries.map(() => '(?,?,?)').join(',');
values = [];
entries.forEach(entry => {
values.push(id, entry.guid, entry.date);
});
connection.query(query, values, err => {
connection.release();
if (err) {
// too late to report as failed
log.error('RSS', err);
}
return callback(null, affected);
});
2016-05-03 16:21:01 +00:00
});
2016-05-03 09:36:06 +00:00
});
});
});
2016-04-04 12:36:30 +00:00
});
});
});
};
module.exports.delete = (id, callback) => {
id = Number(id) || 0;
if (id < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Missing Campaign ID')));
2016-04-04 12:36:30 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('DELETE FROM campaigns WHERE id=? LIMIT 1', [id], (err, result) => {
connection.release();
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
let affected = result && result.affectedRows || 0;
removeCampaignTables(id, err => {
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
db.clearCache('sender', () => {
callback(null, affected);
});
2016-04-04 12:36:30 +00:00
});
});
});
};
2016-04-26 12:18:41 +00:00
module.exports.send = (id, scheduled, callback) => {
2016-04-04 12:36:30 +00:00
module.exports.get(id, false, (err, campaign) => {
if (err) {
return callback(err);
}
if (campaign.status === 2) { // already sending
return callback(null, false);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
2016-04-26 12:18:41 +00:00
let query;
let values;
if (scheduled) {
query = 'UPDATE campaigns SET `status`=2, `scheduled`=?, `status_change`=NOW() WHERE id=? LIMIT 1';
values = [scheduled, id];
} else {
query = 'UPDATE campaigns SET `status`=2, `status_change`=NOW() WHERE id=? LIMIT 1';
values = [id];
}
2016-04-04 12:36:30 +00:00
// campaigns marked as status=2 should be picked up by the sending processes
2016-04-26 12:18:41 +00:00
connection.query(query, values, err => {
connection.release();
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
return callback(null, true);
});
});
});
};
module.exports.pause = (id, callback) => {
module.exports.get(id, false, (err, campaign) => {
if (err) {
return callback(err);
}
if (campaign.status !== 2) {
return callback(null, false);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
// campaigns marked as status=4 are paused
connection.query('UPDATE campaigns SET `status`=4, `status_change`=NOW() WHERE id=? LIMIT 1', [id], err => {
2016-06-22 12:25:36 +00:00
connection.release();
2016-04-04 12:36:30 +00:00
if (err) {
return callback(err);
}
db.clearCache('sender', () => {
callback(null, true);
});
2016-04-04 12:36:30 +00:00
});
});
});
};
module.exports.reset = (id, callback) => {
module.exports.get(id, false, (err, campaign) => {
if (err) {
return callback(err);
}
2016-05-25 20:58:17 +00:00
if (campaign.status !== 3 && campaign.status !== 4 && !(campaign.status === 2 && campaign.scheduled > new Date())) {
2016-04-04 12:36:30 +00:00
return callback(null, false);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('UPDATE campaigns SET `status`=1, `status_change`=NULL, `delivered`=0, `opened`=0, `clicks`=0, `bounced`=0, `complained`=0, `unsubscribed`=0 WHERE id=? LIMIT 1', [id], err => {
if (err) {
connection.release();
return callback(err);
}
2016-05-25 20:58:17 +00:00
db.clearCache('sender', () => {
connection.query('UPDATE links SET `clicks`=0 WHERE campaign=?', [id], err => {
2016-04-04 12:36:30 +00:00
if (err) {
connection.release();
return callback(err);
}
connection.query('TRUNCATE TABLE `campaign__' + id + '`', [id], err => {
2016-04-04 12:36:30 +00:00
if (err) {
connection.release();
2016-04-04 12:36:30 +00:00
return callback(err);
}
connection.query('TRUNCATE TABLE `campaign_tracker__' + id + '`', [id], err => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, true);
});
2016-04-04 12:36:30 +00:00
});
});
});
});
});
2016-05-03 09:36:06 +00:00
});
};
module.exports.activate = (id, callback) => {
module.exports.get(id, false, (err, campaign) => {
if (err) {
return callback(err);
}
if (campaign.status !== 5) {
return callback(null, false);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
// campaigns marked as status=5 are paused
connection.query('UPDATE campaigns SET `status`=6, `status_change`=NOW() WHERE id=? LIMIT 1', [id], err => {
2016-06-22 12:25:36 +00:00
connection.release();
2016-05-03 09:36:06 +00:00
if (err) {
return callback(err);
}
return callback(null, true);
});
});
});
};
module.exports.inactivate = (id, callback) => {
module.exports.get(id, false, (err, campaign) => {
if (err) {
return callback(err);
}
if (campaign.status !== 6) {
return callback(null, false);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
// campaigns marked as status=6 are paused
connection.query('UPDATE campaigns SET `status`=5, `status_change`=NOW() WHERE id=? LIMIT 1', [id], err => {
2016-06-22 12:25:36 +00:00
connection.release();
2016-05-03 09:36:06 +00:00
if (err) {
return callback(err);
}
return callback(null, true);
});
});
2016-04-04 12:36:30 +00:00
});
};
module.exports.getMail = (campaignId, listId, subscriptionId, callback) => {
campaignId = Number(campaignId) || 0;
listId = Number(listId) || 0;
subscriptionId = Number(subscriptionId) || 0;
if (campaignId < 1 || listId < 1 || subscriptionId < 1) {
2017-03-07 14:30:56 +00:00
return callback(new Error(_('Invalid or missing message ID')));
2016-04-04 12:36:30 +00:00
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT * FROM `campaign__' + campaignId + '` WHERE list=? AND subscription=?', [listId, subscriptionId], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let campaign = tools.convertKeys(rows[0]);
return callback(null, campaign);
});
});
};
2016-04-11 03:26:20 +00:00
module.exports.findMailByResponse = (responseId, callback) => {
2016-04-04 12:36:30 +00:00
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT id FROM campaigns', [], (err, campaignList) => {
if (err) {
connection.release();
return callback(err);
}
if (!campaignList || !campaignList.length) {
connection.release();
return callback(null, false);
}
let pos = 0;
let checkNext = () => {
if (pos >= campaignList.length) {
// all campaigns checked, result not found
connection.release();
return callback(null, false);
}
let campaign = campaignList[pos++];
connection.query('SELECT id, list, segment, subscription FROM `campaign__' + campaign.id + '` WHERE `response_id`=? LIMIT 1', [responseId], (err, rows) => {
if (err || !rows || !rows.length) {
return checkNext();
}
connection.release();
2016-04-04 12:36:30 +00:00
let message = rows[0];
message.campaign = campaign.id;
return callback(null, message);
});
};
checkNext();
});
});
};
2016-04-11 03:26:20 +00:00
module.exports.findMailByCampaign = (campaignHeader, callback) => {
if (!campaignHeader) {
return callback(null, false);
}
let parts = campaignHeader.split('.');
let cCid = parts.shift();
let sCid = parts.pop();
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT `id`, `list`, `segment` FROM `campaigns` WHERE `cid`=? LIMIT 1';
connection.query(query, [cCid], (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
if (!rows || !rows.length) {
connection.release();
return callback(null, false);
}
let campaignId = rows[0].id;
let listId = rows[0].list;
let segmentId = rows[0].segment;
let query = 'SELECT id FROM `subscription__' + listId + '` WHERE cid=? LIMIT 1';
connection.query(query, [sCid], (err, rows) => {
if (err) {
connection.release();
return callback(err);
}
if (!rows || !rows.length) {
connection.release();
return callback(null, false);
}
let subscriptionId = rows[0].id;
let query = 'SELECT `id`, `list`, `segment`, `subscription` FROM `campaign__' + campaignId + '` WHERE `list`=? AND `segment`=? AND `subscription`=? LIMIT 1';
connection.query(query, [listId, segmentId, subscriptionId], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let message = rows[0];
message.campaign = campaignId;
return callback(null, message);
});
});
});
});
};
module.exports.updateMessage = (message, status, updateSubscription, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let statusCode;
if (status === 'unsubscribed') {
statusCode = 2;
}
if (status === 'bounced') {
statusCode = 3;
}
if (status === 'complained') {
statusCode = 4;
}
let query = 'UPDATE `campaigns` SET `' + status + '`=`' + status + '`+1 WHERE id=? LIMIT 1';
connection.query(query, [message.campaign], () => {
let query = 'UPDATE `campaign__' + message.campaign + '` SET status=?, updated=NOW() WHERE id=? LIMIT 1';
connection.query(query, [statusCode, message.id], err => {
connection.release();
if (err) {
return callback(err);
}
if (updateSubscription) {
subscriptions.changeStatus(message.subscription, message.list, statusCode === 2 ? message.campaign : false, statusCode, callback);
} else {
return callback(null, true);
}
});
});
});
};
2016-04-04 12:36:30 +00:00
function createCampaignTables(id, callback) {
let query = 'CREATE TABLE `campaign__' + id + '` LIKE campaign';
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query(query, err => {
if (err) {
connection.release();
return callback(err);
}
let query = 'CREATE TABLE `campaign_tracker__' + id + '` LIKE campaign_tracker';
connection.query(query, err => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, true);
});
});
});
}
function removeCampaignTables(id, callback) {
let query = 'DROP TABLE IF EXISTS `campaign__' + id + '`';
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query(query, err => {
if (err) {
connection.release();
return callback(err);
}
let query = 'DROP TABLE IF EXISTS `campaign_tracker__' + id + '`';
connection.query(query, err => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, true);
});
});
});
}