Merge with upstream

This commit is contained in:
vladimir 2017-05-27 14:07:56 +02:00
commit 25bb4afa80
60 changed files with 2177 additions and 1215 deletions

View file

@ -1056,13 +1056,13 @@ module.exports.updateMessage = (message, status, updateSubscription, callback) =
let statusCode;
if (status === 'unsubscribed') {
statusCode = 2;
}
if (status === 'bounced') {
statusCode = 3;
}
if (status === 'complained') {
statusCode = 4;
statusCode = subscriptions.Status.UNSUBSCRIBED;
} else if (status === 'bounced') {
statusCode = subscriptions.Status.BOUNCED;
} else if (status === 'complained') {
statusCode = subscriptions.Status.COMPLAINED;
} else {
return callback(new Error(_('Unrecognized message status')));
}
let query = 'UPDATE `campaigns` SET `' + status + '`=`' + status + '`+1 WHERE id=? LIMIT 1';
@ -1076,7 +1076,7 @@ module.exports.updateMessage = (message, status, updateSubscription, callback) =
}
if (updateSubscription) {
subscriptions.changeStatus(message.subscription, message.list, statusCode === 2 ? message.campaign : false, statusCode, callback);
subscriptions.changeStatus(message.list, message.subscription, statusCode === subscriptions.Status.UNSUBSCRIBED ? message.campaign : false, statusCode, callback);
} else {
return callback(null, true);
}

View file

@ -0,0 +1,91 @@
'use strict';
let db = require('../db');
let shortid = require('shortid');
let helpers = require('../helpers');
let _ = require('../translate')._;
/*
Adds new entry to the confirmations tables. Generates confirmation cid, which it returns.
*/
module.exports.addConfirmation = (listId, action, ip, data, callback) => {
let cid = shortid.generate();
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'INSERT INTO confirmations (cid, list, action, ip, data) VALUES (?,?,?,?,?)';
connection.query(query, [cid, listId, action, ip, JSON.stringify(data || {})], (err, result) => {
connection.release();
if (err) {
return callback(err);
}
if (!result || !result.affectedRows) {
return callback(new Error(_('Could not store confirmation data')));
}
return callback(null, cid);
});
});
};
/*
Atomically retrieves confirmation from the database, removes it from the database and returns it.
*/
module.exports.takeConfirmation = (cid, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.beginTransaction(err => {
if (err) {
connection.release();
return callback(err);
}
let query = 'SELECT cid, list, action, ip, data FROM confirmations WHERE cid=? LIMIT 1';
connection.query(query, [cid], (err, rows) => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (!rows || !rows.length) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, false));
}
connection.query('DELETE FROM confirmations WHERE `cid`=? LIMIT 1', [cid], () => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.commit(err => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
let data;
try {
data = JSON.parse(rows[0].data);
} catch (E) {
data = {};
}
const result = {
listId: rows[0].list,
action: rows[0].action,
ip: rows[0].ip,
data
};
return callback(null, result);
});
});
});
});
});
};

View file

@ -14,22 +14,31 @@ let allowedKeys = [
'fields_shown_on_manage',
'layout',
'form_input_style',
'mail_confirm_html',
'mail_confirm_text',
'web_subscribe',
'web_confirm_subscription_notice',
'mail_confirm_subscription_html',
'mail_confirm_subscription_text',
'mail_already_subscribed_html',
'mail_already_subscribed_text',
'web_subscribed_notice',
'mail_subscription_confirmed_html',
'mail_subscription_confirmed_text',
'mail_unsubscribe_confirmed_html',
'mail_unsubscribe_confirmed_text',
'web_confirm_notice',
'web_manage_address',
'web_manage',
'web_subscribe',
'web_subscribed',
'web_unsubscribe_notice',
'web_manage_address',
'web_updated_notice',
'web_unsubscribe',
'web_updated_notice'
'web_confirm_unsubscription_notice',
'mail_confirm_unsubscription_html',
'mail_confirm_unsubscription_text',
'mail_confirm_address_change_html',
'mail_confirm_address_change_text',
'web_unsubscribed_notice',
'mail_unsubscription_confirmed_html',
'mail_unsubscription_confirmed_text',
'web_manual_unsubscribe_notice'
];
module.exports.list = (listId, callback) => {
listId = Number(listId) || 0;

View file

@ -7,7 +7,18 @@ let segments = require('./segments');
let _ = require('../translate')._;
let tableHelpers = require('../table-helpers');
let allowedKeys = ['description', 'default_form', 'public_subscribe'];
const UnsubscriptionMode = {
ONE_STEP: 0,
ONE_STEP_WITH_FORM: 1,
TWO_STEP: 2,
TWO_STEP_WITH_FORM: 3,
MANUAL: 4,
MAX: 5
};
module.exports.UnsubscriptionMode = UnsubscriptionMode;
let allowedKeys = ['description', 'default_form', 'public_subscribe', 'unsubscription_mode'];
module.exports.list = (start, limit, callback) => {
tableHelpers.list('lists', ['*'], 'name', null, start, limit, callback);
@ -99,6 +110,63 @@ module.exports.get = (id, callback) => {
});
};
module.exports.update = (id, updates, callback) => {
updates = updates || {};
id = Number(id) || 0;
if (id < 1) {
return callback(new Error(_('Missing List ID')));
}
const data = tools.convertKeys(updates);
const keys = [];
const values = [];
// The update can be only partial when executed from forms/:list
if (!data.customFormChangeOnly) {
data.publicSubscribe = data.publicSubscribe ? 1 : 0;
data.unsubscriptionMode = Number(data.unsubscriptionMode);
let name = (data.name || '').toString().trim();
if (!name) {
return callback(new Error(_('List Name must be set')));
}
keys.push('name');
values.push(name);
}
Object.keys(data).forEach(key => {
let value = data[key].toString().trim();
key = tools.toDbKey(key);
if (key === 'description') {
value = tools.purifyHTML(value);
}
if (allowedKeys.indexOf(key) >= 0) {
keys.push(key);
values.push(value);
}
});
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
values.push(id);
connection.query('UPDATE lists SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, result && result.affectedRows || false);
});
});
};
module.exports.create = (list, callback) => {
let data = tools.convertKeys(list);
@ -157,54 +225,6 @@ module.exports.create = (list, callback) => {
});
};
module.exports.update = (id, updates, callback) => {
updates = updates || {};
id = Number(id) || 0;
let data = tools.convertKeys(updates);
data.publicSubscribe = data.publicSubscribe ? 1 : 0;
let name = (data.name || '').toString().trim();
let keys = ['name'];
let values = [name];
if (id < 1) {
return callback(new Error(_('Missing List ID')));
}
if (!name) {
return callback(new Error(_('List Name must be set')));
}
Object.keys(data).forEach(key => {
let value = data[key].toString().trim();
key = tools.toDbKey(key);
if (key === 'description') {
value = tools.purifyHTML(value);
}
if (allowedKeys.indexOf(key) >= 0) {
keys.push(key);
values.push(value);
}
});
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
values.push(id);
connection.query('UPDATE lists SET ' + keys.map(key => key + '=?').join(', ') + ' WHERE id=? LIMIT 1', values, (err, result) => {
connection.release();
if (err) {
return callback(err);
}
return callback(null, result && result.affectedRows || false);
});
});
};
module.exports.delete = (id, callback) => {
id = Number(id) || 0;

View file

@ -13,7 +13,8 @@ const ReportState = {
SCHEDULED: 0,
PROCESSING: 1,
FINISHED: 2,
FAILED: 3
FAILED: 3,
MAX: 4
};
module.exports.ReportState = ReportState;
@ -245,8 +246,8 @@ module.exports.getCampaignResults = (campaign, select, clause, callback) => {
const query = 'SELECT ' + selFields.join(', ') + ' FROM `subscription__' + campaign.list + '` subscribers INNER JOIN `campaign__' + campaign.id + '` campaign on subscribers.id=campaign.subscription LEFT JOIN `campaign_tracker__' + campaign.id + '` tracker on subscribers.id=tracker.subscriber ' + clause;
connection.query(query, (err, results) => {
connection.release();
if (err) {
connection.release();
return callback(err);
}

View file

@ -15,6 +15,8 @@ function listValues(filter, callback) {
filter = false;
}
// TODO: It would be good to cache the settings. It feels awkward to always go to DB to retrieve something what is essentially a constant
filter = [].concat(filter || []).map(key => tools.toDbKey(key));
db.getConnection((err, connection) => {

View file

@ -5,16 +5,20 @@ let shortid = require('shortid');
let tools = require('../tools');
let helpers = require('../helpers');
let fields = require('./fields');
let geoip = require('geoip-ultralight');
let segments = require('./segments');
let settings = require('./settings');
let mailer = require('../mailer');
let urllib = require('url');
let log = require('npmlog');
let _ = require('../translate')._;
let util = require('util');
let tableHelpers = require('../table-helpers');
const Status = {
SUBSCRIBED: 1,
UNSUBSCRIBED: 2,
BOUNCED: 3,
COMPLAINED: 4,
MAX: 5
};
module.exports.Status = Status;
module.exports.list = (listId, start, limit, callback) => {
listId = Number(listId) || 0;
if (!listId) {
@ -88,197 +92,17 @@ module.exports.filter = (listId, request, columns, segmentId, callback) => {
};
module.exports.addConfirmation = (list, email, optInIp, data, callback) => {
let cid = shortid.generate();
tools.validateEmail(email, false, err => {
if (err) {
return callback(err);
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'INSERT INTO confirmations (cid, list, email, opt_in_ip, data) VALUES (?,?,?,?,?)';
connection.query(query, [cid, list.id, email, optInIp, JSON.stringify(data || {})], (err, result) => {
connection.release();
if (err) {
return callback(err);
}
if (!result || !result.affectedRows) {
return callback(null, false);
}
fields.list(list.id, (err, fieldList) => {
if (err) {
return callback(err);
}
let encryptionKeys = [];
fields.getRow(fieldList, data).forEach(field => {
if (field.type === 'gpg' && field.value) {
encryptionKeys.push(field.value.trim());
}
});
settings.list(['defaultHomepage', 'defaultFrom', 'defaultAddress', 'defaultPostaddress', 'serviceUrl'], (err, configItems) => {
if (err) {
return callback(err);
}
setImmediate(() => {
if (data._skip) {
log.info('Subscription', 'Confirmation message for %s marked to be skipped (%s)', email, JSON.stringify(data));
return;
}
let sendMail = (html, text) => {
mailer.sendMail({
from: {
name: configItems.defaultFrom,
address: configItems.defaultAddress
},
to: {
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
address: email
},
subject: util.format(_('%s: Please Confirm Subscription'), list.name),
encryptionKeys
}, {
html,
text,
data: {
title: list.name,
contactAddress: configItems.defaultAddress,
defaultPostaddress: configItems.defaultPostaddress,
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid)
}
}, err => {
if (err) {
log.error('Subscription', err);
}
});
};
let text = {
template: 'subscription/mail-confirm-text.hbs'
};
let html = {
template: 'subscription/mail-confirm-html.mjml.hbs',
layout: 'subscription/layout.mjml.hbs',
type: 'mjml'
};
helpers.injectCustomFormTemplates(list.defaultForm, { text, html }, (err, tmpl) => {
if (err) {
return sendMail(html, text);
}
sendMail(tmpl.html, tmpl.text);
});
});
return callback(null, cid);
});
});
});
});
});
};
module.exports.subscribe = (cid, optInIp, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'SELECT * FROM confirmations WHERE cid=? LIMIT 1';
connection.query(query, [cid], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length) {
return callback(null, false);
}
let subscription;
let listId = rows[0].list;
let email = rows[0].email;
try {
subscription = JSON.parse(rows[0].data);
} catch (E) {
subscription = {};
}
if (subscription.action === 'update' && subscription.subscriber) {
// update email address instead of adding new
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
let query = 'UPDATE `subscription__' + listId + '` SET `email`=? WHERE `id`=? LIMIT 1';
let args = [email, subscription.subscriber];
connection.query(query, args, err => {
if (err) {
connection.release();
return callback(err);
}
connection.query('DELETE FROM confirmations WHERE `cid`=? LIMIT 1', [cid], () => {
connection.release();
// reload full data from db in case it was an update, not insert
return module.exports.getById(listId, subscription.subscriber, callback);
});
});
});
return;
}
subscription.cid = cid;
subscription.list = listId;
subscription.email = email;
let optInCountry = geoip.lookupCountry(optInIp) || null;
module.exports.insert(listId, {
email,
cid,
optInIp,
optInCountry,
status: 1
}, subscription, (err, result) => {
if (err) {
return callback(err);
}
if (!result.entryId) {
return callback(new Error(_('Could not save subscription')));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('DELETE FROM confirmations WHERE `cid`=? LIMIT 1', [cid], () => {
connection.release();
// reload full data from db in case it was an update, not insert
return module.exports.getById(listId, result.entryId, callback);
});
});
});
});
});
};
module.exports.insert = (listId, meta, subscription, callback) => {
/*
Adds a new subscription. Returns error if a subscription with the same email address is already present and is not unsubscribed.
If it is unsubscribed, the existing subscription is changed based on the provided data.
If meta.partial is true, it updates even an active subscription.
*/
module.exports.insert = (listId, meta, subscriptionData, callback) => {
meta = tools.convertKeys(meta);
subscription = tools.convertKeys(subscription);
subscriptionData = tools.convertKeys(subscriptionData);
meta.email = meta.email || subscription.email;
meta.email = meta.email || subscriptionData.email;
meta.cid = meta.cid || shortid.generate();
fields.list(listId, (err, fieldList) => {
@ -292,8 +116,8 @@ module.exports.insert = (listId, meta, subscription, callback) => {
let values = [];
let allowedKeys = ['first_name', 'last_name', 'tz', 'is_test'];
Object.keys(subscription).forEach(key => {
let value = subscription[key];
Object.keys(subscriptionData).forEach(key => {
let value = subscriptionData[key];
key = tools.toDbKey(key);
if (key === 'tz') {
value = (value || '').toString().toLowerCase().trim();
@ -307,7 +131,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
}
});
fields.getValues(fields.getRow(fieldList, subscription, true, true, !!meta.partial), true).forEach(field => {
fields.getValues(fields.getRow(fieldList, subscriptionData, true, true, !!meta.partial), true).forEach(field => {
keys.push(field.key);
values.push(field.value);
});
@ -326,10 +150,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
let query = 'SELECT `id`, `status`, `cid` FROM `subscription__' + listId + '` WHERE `email`=? OR `cid`=? LIMIT 1';
connection.query(query, [meta.email, meta.cid], (err, rows) => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
let query;
@ -338,25 +159,26 @@ module.exports.insert = (listId, meta, subscription, callback) => {
let entryId = existing ? existing.id : false;
meta.cid = existing ? rows[0].cid : meta.cid;
meta.status = meta.status || (existing ? existing.status : 1);
meta.status = meta.status || (existing ? existing.status : Status.SUBSCRIBED);
let statusChange = !existing || existing.status !== meta.status;
let statusDirection;
if (existing && existing.status === Status.SUBSCRIBED && !meta.partial) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Email address already registered'))));
}
if (statusChange) {
keys.push('status', 'status_change');
values.push(meta.status, new Date());
statusDirection = !existing ? (meta.status === 1 ? '+' : false) : (existing.status === 1 ? '-' : '+');
statusDirection = !existing ? (meta.status === Status.SUBSCRIBED ? '+' : false) : (existing.status === Status.SUBSCRIBED ? '-' : '+');
}
if (!keys.length) {
// nothing to update
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, {
@ -380,10 +202,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
connection.query(query, queryArgs, (err, result) => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
entryId = result.insertId || entryId;
@ -391,17 +210,11 @@ module.exports.insert = (listId, meta, subscription, callback) => {
if (statusChange && statusDirection) {
connection.query('UPDATE lists SET `subscribers`=`subscribers`' + statusDirection + '1 WHERE id=?', [listId], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, {
@ -414,10 +227,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
} else {
connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, {
@ -575,7 +385,7 @@ module.exports.update = (listId, cid, updates, allowEmail, callback) => {
}
if (!cid) {
return callback(new Error(_('Missing subscription ID')));
return callback(new Error(_('Missing Subscription ID')));
}
fields.list(listId, (err, fieldList) => {
@ -627,46 +437,7 @@ module.exports.update = (listId, cid, updates, allowEmail, callback) => {
});
};
module.exports.unsubscribe = (listId, email, campaignId, callback) => {
listId = Number(listId) || 0;
email = (email || '').toString().trim();
campaignId = (campaignId || '').toString().trim() || false;
if (listId < 1) {
return callback(new Error(_('Missing List ID')));
}
if (!email) {
return callback(new Error(_('Missing email address')));
}
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.query('SELECT * FROM `subscription__' + listId + '` WHERE `email`=?', [email], (err, rows) => {
connection.release();
if (err) {
return callback(err);
}
if (!rows || !rows.length || rows[0].status !== 1) {
return callback(null, false);
}
let subscription = tools.convertKeys(rows[0]);
module.exports.changeStatus(subscription.id, listId, campaignId, 2, err => {
if (err) {
return callback(err);
}
return callback(null, subscription);
});
});
});
};
module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
module.exports.changeStatus = (listId, id, campaignId, status, callback) => {
db.getConnection((err, connection) => {
if (err) {
return callback(err);
@ -679,17 +450,11 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
connection.query('SELECT `status` FROM `subscription__' + listId + '` WHERE id=? LIMIT 1', [id], (err, rows) => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (!rows || !rows.length) {
return connection.rollback(() => {
connection.release();
return callback(null, false);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, false));
}
let oldStatus = rows[0].status;
@ -697,31 +462,22 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
let statusDirection;
if (!statusChange) {
return connection.rollback(() => {
connection.release();
return callback(null, true);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(null, true));
}
if (statusChange && oldStatus === 1 || status === 1) {
statusDirection = status === 1 ? '+' : '-';
if (statusChange && oldStatus === Status.SUBSCRIBED || status === Status.SUBSCRIBED) {
statusDirection = status === Status.SUBSCRIBED ? '+' : '-';
}
connection.query('UPDATE `subscription__' + listId + '` SET `status`=?, `status_change`=NOW() WHERE id=? LIMIT 1', [status, id], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (!statusDirection) {
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, true);
@ -730,20 +486,14 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
connection.query('UPDATE `lists` SET `subscribers`=`subscribers`' + statusDirection + '1 WHERE id=? LIMIT 1', [listId], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
// status change is not related to a campaign or it marks message as bounced etc.
if (!campaignId || status > 2) {
if (!campaignId || status !== Status.SUBSCRIBED) {
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, true);
@ -752,10 +502,7 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
connection.query('SELECT `id` FROM `campaigns` WHERE `cid`=? LIMIT 1', [campaignId], (err, rows) => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
let campaign = rows && rows[0] || false;
@ -764,10 +511,7 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
// should not happend
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, true);
@ -775,12 +519,9 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
}
// we should see only unsubscribe events here but you never know
connection.query('UPDATE `campaigns` SET `unsubscribed`=`unsubscribed`' + (status === 2 ? '+' : '-') + '1 WHERE `cid`=? LIMIT 1', [campaignId], err => {
connection.query('UPDATE `campaigns` SET `unsubscribed`=`unsubscribed`' + (status === Status.UNSUBSCRIBED ? '+' : '-') + '1 WHERE `cid`=? LIMIT 1', [campaignId], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
let query = 'UPDATE `campaign__' + campaign.id + '` SET `status`=? WHERE `list`=? AND `subscription`=? LIMIT 1';
@ -789,18 +530,12 @@ module.exports.changeStatus = (id, listId, campaignId, status, callback) => {
// Updated tracker status
connection.query(query, values, err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, true);
@ -852,19 +587,13 @@ module.exports.delete = (listId, cid, callback) => {
connection.query('DELETE FROM `subscription__' + listId + '` WHERE cid=? LIMIT 1', [cid], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (subscription.status !== 1) {
if (subscription.status !== Status.SUBSCRIBED) {
return connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, subscription.email);
@ -873,17 +602,11 @@ module.exports.delete = (listId, cid, callback) => {
connection.query('UPDATE lists SET subscribers=subscribers-1 WHERE id=? LIMIT 1', [listId], err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.commit(err => {
if (err) {
return connection.rollback(() => {
connection.release();
return callback(err);
});
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback(null, subscription.email);
@ -963,11 +686,10 @@ module.exports.updateImport = (listId, importId, data, callback) => {
connection.release();
return callback(null, affected);
});
return;
} else {
connection.release();
return callback(null, affected);
}
connection.release();
return callback(null, affected);
});
});
};
@ -1075,13 +797,13 @@ module.exports.listImports = (listId, callback) => {
});
};
module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
updates = tools.convertKeys(updates);
/*
Performs checks before update of an address. This includes finding the existing subscriber, validating the new email
and checking whether the new email does not conflict with other subscribers.
*/
module.exports.updateAddressCheck = (list, cid, emailNew, ip, callback) => {
cid = (cid || '').toString().trim();
let emailNew = (updates.emailNew || '').toString().trim();
if (!list || !list.id) {
return callback(new Error(_('Missing List ID')));
}
@ -1100,7 +822,7 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
return callback(err);
}
let query = 'SELECT `id`, `email` FROM `subscription__' + list.id + '` WHERE `cid`=? LIMIT 1';
let query = 'SELECT * FROM `subscription__' + list.id + '` WHERE `cid`=? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
let args = [cid];
connection.query(query, args, (err, rows) => {
if (err) {
@ -1119,7 +841,7 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
let old = rows[0];
let query = 'SELECT `id` FROM `subscription__' + list.id + '` WHERE `email`=? AND `cid`<>? LIMIT 1';
let query = 'SELECT `id` FROM `subscription__' + list.id + '` WHERE `email`=? AND `cid`<>? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
let args = [emailNew, cid];
connection.query(query, args, (err, rows) => {
connection.release();
@ -1127,18 +849,77 @@ module.exports.updateAddress = (list, cid, updates, optInIp, callback) => {
return callback(err);
}
if (rows && rows[0] && rows[0].id) {
return callback(new Error(_('This address is already registered by someone else')));
if (rows && rows.length > 0) {
return callback(null, old, false);
} else {
return callback(null, old, true);
}
module.exports.addConfirmation(list, emailNew, optInIp, {
action: 'update',
cid,
subscriber: old.id,
emailOld: old.email
}, callback);
});
});
});
});
};
/*
Updates address in subscription__xxx
*/
module.exports.updateAddress = (listId, subscriptionId, emailNew, callback) => {
// update email address instead of adding new
db.getConnection((err, connection) => {
if (err) {
return callback(err);
}
connection.beginTransaction(err => {
if (err) {
connection.release();
return callback(err);
}
let query = 'SELECT `id` FROM `subscription__' + listId + '` WHERE `email`=? AND `id`<>? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
let args = [emailNew, subscriptionId];
connection.query(query, args, (err, rows) => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (rows && rows.length > 0) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Email address already registered'))));
}
let query = 'DELETE FROM `subscription__' + listId + '` WHERE `email`=? AND `id`<>?';
let args = [emailNew, subscriptionId];
connection.query(query, args, err => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
let query = 'UPDATE `subscription__' + listId + '` SET `email`=? WHERE `id`=? AND `status`=' + Status.SUBSCRIBED + ' LIMIT 1';
let args = [emailNew, subscriptionId];
connection.query(query, args, (err, result) => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
if (!result || !result.affectedRows) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(new Error(_('Subscription not found in this list'))));
}
return connection.commit(err => {
if (err) {
return helpers.rollbackAndReleaseConnection(connection, () => callback(err));
}
connection.release();
return callback();
});
});
});
});
});
});
};
module.exports.getUnsubscriptionMode = (list, subscriptionId) => list.unsubscriptionMode; // eslint-disable-line no-unused-vars
// TODO: Once the unsubscription mode is customizable per segment, then this will be a good place to process it.