v1.7.0
This commit is contained in:
parent
bc902f8db7
commit
c26f8b15d7
6 changed files with 127 additions and 81 deletions
|
@ -1,5 +1,9 @@
|
|||
# Changelog
|
||||
|
||||
## 1.7.0 2016-05-11
|
||||
|
||||
* Updated API, added new option **REQUIRE_CONFIRMATION** for subscriptions to send confirmation email before subscribing
|
||||
|
||||
## 1.6.0 2016-05-07
|
||||
|
||||
* Added simple API support for adding and removing list subscriptions
|
||||
|
|
|
@ -6,6 +6,10 @@ let tools = require('../tools');
|
|||
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');
|
||||
|
||||
module.exports.list = (listId, start, limit, callback) => {
|
||||
listId = Number(listId) || 0;
|
||||
|
@ -131,7 +135,7 @@ module.exports.filter = (listId, request, columns, segmentId, callback) => {
|
|||
};
|
||||
|
||||
|
||||
module.exports.addConfirmation = (listId, email, data, callback) => {
|
||||
module.exports.addConfirmation = (list, email, data, callback) => {
|
||||
let cid = shortid.generate();
|
||||
|
||||
tools.validateEmail(email, false, err => {
|
||||
|
@ -145,12 +149,64 @@ module.exports.addConfirmation = (listId, email, data, callback) => {
|
|||
}
|
||||
|
||||
let query = 'INSERT INTO confirmations (cid, list, email, data) VALUES (?,?,?,?)';
|
||||
connection.query(query, [cid, listId, email, JSON.stringify(data || {})], (err, result) => {
|
||||
connection.query(query, [cid, list.id, email, JSON.stringify(data || {})], (err, result) => {
|
||||
connection.release();
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, result && cid || false);
|
||||
|
||||
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', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
setImmediate(() => {
|
||||
|
||||
mailer.sendMail({
|
||||
from: {
|
||||
name: configItems.defaultFrom,
|
||||
address: configItems.defaultAddress
|
||||
},
|
||||
to: {
|
||||
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
|
||||
address: email
|
||||
},
|
||||
subject: list.name + ': Please Confirm Subscription',
|
||||
encryptionKeys
|
||||
}, {
|
||||
html: 'emails/confirm-html.hbs',
|
||||
text: 'emails/confirm-text.hbs',
|
||||
data: {
|
||||
title: list.name,
|
||||
contactAddress: configItems.defaultAddress,
|
||||
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + cid)
|
||||
}
|
||||
}, err => {
|
||||
if (err) {
|
||||
log.error('Subscription', err.stack);
|
||||
}
|
||||
});
|
||||
});
|
||||
return callback(null, cid);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -263,7 +319,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
|
|||
return callback(err);
|
||||
}
|
||||
|
||||
let query = 'SELECT id, status FROM `subscription__' + listId + '` WHERE email=? OR cid=? LIMIT 1';
|
||||
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(() => {
|
||||
|
@ -277,6 +333,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
|
|||
let existing = rows && rows[0] || false;
|
||||
let entryId = existing ? existing.id : false;
|
||||
|
||||
meta.cid = existing ? rows[0].cid : meta.cid;
|
||||
meta.status = meta.status || (existing ? existing.status : 1);
|
||||
|
||||
let statusChange = !existing || existing.status !== meta.status;
|
||||
|
@ -327,6 +384,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
|
|||
connection.release();
|
||||
return callback(null, {
|
||||
entryId,
|
||||
cid: meta.cid,
|
||||
inserted: !existing
|
||||
});
|
||||
});
|
||||
|
@ -342,6 +400,7 @@ module.exports.insert = (listId, meta, subscription, callback) => {
|
|||
connection.release();
|
||||
return callback(null, {
|
||||
entryId,
|
||||
cid: meta.cid,
|
||||
inserted: !existing
|
||||
});
|
||||
});
|
||||
|
|
14
package.json
14
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "mailtrain",
|
||||
"private": true,
|
||||
"version": "1.6.0",
|
||||
"version": "1.7.0",
|
||||
"description": "Self hosted email newsletter app",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "^1.15.0",
|
||||
"body-parser": "^1.15.1",
|
||||
"bounce-handler": "^7.3.2-fork.0",
|
||||
"compression": "^1.6.1",
|
||||
"config": "^1.20.1",
|
||||
|
@ -50,16 +50,16 @@
|
|||
"humanize": "0.0.9",
|
||||
"is-url": "^1.2.1",
|
||||
"isemail": "^2.1.0",
|
||||
"jsdom": "^8.5.0",
|
||||
"juice": "^1.10.0",
|
||||
"moment-timezone": "^0.5.3",
|
||||
"jsdom": "^9.0.0",
|
||||
"juice": "^1.11.0",
|
||||
"moment-timezone": "^0.5.4",
|
||||
"morgan": "^1.7.0",
|
||||
"multer": "^1.1.0",
|
||||
"mysql": "^2.10.2",
|
||||
"nodemailer": "^2.3.2",
|
||||
"nodemailer": "^2.4.1",
|
||||
"nodemailer-openpgp": "^1.0.2",
|
||||
"npmlog": "^2.0.3",
|
||||
"openpgp": "^2.2.2",
|
||||
"openpgp": "^2.3.0",
|
||||
"passport": "^0.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"request": "^2.72.0",
|
||||
|
|
|
@ -114,26 +114,43 @@ router.post('/subscribe/:listId', (req, res) => {
|
|||
partial: true
|
||||
};
|
||||
|
||||
if (input.FORCE_SUBSCRIBE === 'yes') {
|
||||
if (/^(yes|true|1)$/i.test(input.FORCE_SUBSCRIBE)) {
|
||||
meta.status = 1;
|
||||
}
|
||||
|
||||
subscriptions.insert(list.id, meta, subscription, (err, response) => {
|
||||
if (err) {
|
||||
res.status(500);
|
||||
return res.json({
|
||||
error: err.message || err,
|
||||
data: []
|
||||
});
|
||||
}
|
||||
res.status(200);
|
||||
res.json({
|
||||
data: {
|
||||
id: response.entryId,
|
||||
subscribed: true
|
||||
if (/^(yes|true|1)$/i.test(input.REQUIRE_CONFIRMATION)) {
|
||||
subscriptions.addConfirmation(list, input.EMAIL, subscription, (err, cid) => {
|
||||
if (err) {
|
||||
res.status(500);
|
||||
return res.json({
|
||||
error: err.message || err,
|
||||
data: []
|
||||
});
|
||||
}
|
||||
res.status(200);
|
||||
res.json({
|
||||
data: {
|
||||
id: cid
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
subscriptions.insert(list.id, meta, subscription, (err, response) => {
|
||||
if (err) {
|
||||
res.status(500);
|
||||
return res.json({
|
||||
error: err.message || err,
|
||||
data: []
|
||||
});
|
||||
}
|
||||
res.status(200);
|
||||
res.json({
|
||||
data: {
|
||||
id: response.cid
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -230,7 +230,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
|
|||
});
|
||||
data = tools.convertKeys(data);
|
||||
|
||||
subscriptions.addConfirmation(list.id, email, data, (err, confirmCid) => {
|
||||
subscriptions.addConfirmation(list, email, data, (err, confirmCid) => {
|
||||
if (!err && !confirmCid) {
|
||||
err = new Error('Could not store confirmation data');
|
||||
}
|
||||
|
@ -239,51 +239,7 @@ router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req
|
|||
return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err) {
|
||||
return next(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', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.redirect('/subscription/' + req.params.cid + '/confirm-notice');
|
||||
|
||||
mailer.sendMail({
|
||||
from: {
|
||||
name: configItems.defaultFrom,
|
||||
address: configItems.defaultAddress
|
||||
},
|
||||
to: {
|
||||
name: [].concat(data.firstName || []).concat(data.lastName || []).join(' '),
|
||||
address: email
|
||||
},
|
||||
subject: list.name + ': Please Confirm Subscription',
|
||||
encryptionKeys
|
||||
}, {
|
||||
html: 'emails/confirm-html.hbs',
|
||||
text: 'emails/confirm-text.hbs',
|
||||
data: {
|
||||
title: list.name,
|
||||
contactAddress: configItems.defaultAddress,
|
||||
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + confirmCid)
|
||||
}
|
||||
}, err => {
|
||||
if (err) {
|
||||
log.error('Subscription', err.stack);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
res.redirect('/subscription/' + req.params.cid + '/confirm-notice');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -60,14 +60,24 @@
|
|||
<strong>POST</strong> arguments
|
||||
</p>
|
||||
<ul>
|
||||
<li><strong>EMAIL</strong> – subscriber's email address (<em>required</em>)
|
||||
<li><strong>FIRST_NAME</strong> – subscriber's first name</li>
|
||||
<li><strong>LAST_NAME</strong> – subscriber's last name</li>
|
||||
<li><strong>TIMEZONE</strong> – subscriber's timezone (eg. "Europe/Tallinn", "PST" or "UTC"). If not set defaults to "UTC"</li>
|
||||
<li><strong>MERGE_TAG_VALUE</strong> – custom field value. Use yes/no for option group values (checkboxes, radios, drop downs)</li>
|
||||
<li>
|
||||
<strong>FORCE_SUBSCRIBE</strong> – set to "yes" if you want to make sure the email is marked as subscribed even if it was previously marked as unsubscribed. By default if the email was already unsubscribed then subscription status is not changed.
|
||||
</li>
|
||||
<li><strong>EMAIL</strong> – subscriber's email address (<em>required</em>)</li>
|
||||
<li><strong>FIRST_NAME</strong> – subscriber's first name</li>
|
||||
<li><strong>LAST_NAME</strong> – subscriber's last name</li>
|
||||
<li><strong>TIMEZONE</strong> – subscriber's timezone (eg. "Europe/Tallinn", "PST" or "UTC"). If not set defaults to "UTC"</li>
|
||||
<li><strong>MERGE_TAG_VALUE</strong> – custom field value. Use yes/no for option group values (checkboxes, radios, drop downs)</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Additional <strong>POST</strong> arguments:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>FORCE_SUBSCRIBE</strong> – set to "yes" if you want to make sure the email is marked as subscribed even if it was previously marked as unsubscribed. If the email was already unsubscribed/blocked then subscription status is not changed by default.
|
||||
</li>
|
||||
<li>
|
||||
<strong>REQUIRE_CONFIRMATION</strong> – set to "yes" if you want to send confirmation email to the subscriber before actually marking as subscribed
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
|
@ -75,7 +85,7 @@
|
|||
</p>
|
||||
|
||||
<pre>curl -XPOST {{serviceUrl}}api/subscribe/B16uVTdW?access_token={{accessToken}}\
|
||||
--data 'EMAIL=test@example.com&MERGE_CHECKBOX=yes'</pre>
|
||||
--data 'EMAIL=test@example.com&MERGE_CHECKBOX=yes&REQUIRE_CONFIRMATION=yes'</pre>
|
||||
|
||||
<h3>POST /api/unsubscribe/:listId – Remove subscription</h3>
|
||||
|
||||
|
|
Loading…
Reference in a new issue