Merge with upstream
This commit is contained in:
commit
25bb4afa80
60 changed files with 2177 additions and 1215 deletions
|
@ -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);
|
||||
}
|
||||
|
|
91
lib/models/confirmations.js
Normal file
91
lib/models/confirmations.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue