Initial import
This commit is contained in:
commit
54fa30701e
278 changed files with 37868 additions and 0 deletions
80
routes/archive.js
Normal file
80
routes/archive.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
let settings = require('../lib/models/settings');
|
||||
let campaigns = require('../lib/models/campaigns');
|
||||
let lists = require('../lib/models/lists');
|
||||
let subscriptions = require('../lib/models/subscriptions');
|
||||
let tools = require('../lib/tools');
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
|
||||
router.get('/:campaign/:list/:subscription', (req, res, next) => {
|
||||
settings.get('serviceUrl', (err, serviceUrl) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
campaigns.getByCid(req.params.campaign, (err, campaign) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!campaign) {
|
||||
err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
lists.getByCid(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
subscriptions.get(list.id, req.params.subscription, (err, subscription) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!subscription) {
|
||||
err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
campaigns.getMail(campaign.id, list.id, subscription.id, (err, mail) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!mail) {
|
||||
err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('archive/view', {
|
||||
layout: 'archive/layout',
|
||||
message: tools.formatMessage(serviceUrl, campaign, list, subscription, campaign.html),
|
||||
campaign,
|
||||
list,
|
||||
subscription
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
288
routes/campaigns.js
Normal file
288
routes/campaigns.js
Normal file
|
@ -0,0 +1,288 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let lists = require('../lib/models/lists');
|
||||
let templates = require('../lib/models/templates');
|
||||
let campaigns = require('../lib/models/campaigns');
|
||||
let settings = require('../lib/models/settings');
|
||||
let tools = require('../lib/tools');
|
||||
let striptags = require('striptags');
|
||||
let passport = require('../lib/passport');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('campaigns');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
let limit = 999999999;
|
||||
let start = 0;
|
||||
|
||||
campaigns.list(start, limit, (err, rows, total) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
res.render('campaigns/campaigns', {
|
||||
rows: rows.map((row, i) => {
|
||||
row.index = start + i + 1;
|
||||
row.description = striptags(row.description);
|
||||
switch (row.status) {
|
||||
case 1:
|
||||
row.statusText = 'Idling';
|
||||
break;
|
||||
case 2:
|
||||
row.statusText = 'Sending';
|
||||
break;
|
||||
case 3:
|
||||
row.statusText = 'Finished';
|
||||
break;
|
||||
case 4:
|
||||
row.statusText = 'Paused';
|
||||
break;
|
||||
}
|
||||
row.createdTimestamp = row.created.getTime();
|
||||
row.created = row.created.toISOString();
|
||||
return row;
|
||||
}),
|
||||
total
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/create', passport.csrfProtection, (req, res) => {
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
if (/^\d+:\d+$/.test(data.list)) {
|
||||
data.segment = Number(data.list.split(':').pop());
|
||||
data.list = Number(data.list.split(':').shift());
|
||||
}
|
||||
|
||||
settings.list(['defaultFrom', 'defaultAddress', 'defaultSubject'], (err, configItems) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
lists.quicklist((err, listItems) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (Number(data.list)) {
|
||||
listItems.forEach(list => {
|
||||
list.segments.forEach(segment => {
|
||||
if (segment.id === data.segment) {
|
||||
segment.selected = true;
|
||||
}
|
||||
});
|
||||
if (list.id === data.list && !data.segment) {
|
||||
list.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
templates.quicklist((err, templateItems) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (Number(data.template)) {
|
||||
templateItems.forEach(item => {
|
||||
if (item.id === Number(data.template)) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.listItems = listItems;
|
||||
data.templateItems = templateItems;
|
||||
|
||||
data.from = data.from || configItems.defaultFrom;
|
||||
data.address = data.address || configItems.defaultAddress;
|
||||
data.subject = data.subject || configItems.defaultSubject;
|
||||
|
||||
res.render('campaigns/create', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.create(req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create campaign');
|
||||
return res.redirect('/campaigns/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'Campaign “' + req.body.name + '” created');
|
||||
res.redirect('/campaigns/edit/' + id + '?tab=template');
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/edit/:id', passport.csrfProtection, (req, res) => {
|
||||
campaigns.get(req.params.id, false, (err, campaign) => {
|
||||
if (err || !campaign) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
|
||||
return res.redirect('/campaigns');
|
||||
}
|
||||
|
||||
lists.quicklist((err, listItems) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (Number(campaign.list)) {
|
||||
listItems.forEach(list => {
|
||||
list.segments.forEach(segment => {
|
||||
if (segment.id === campaign.segment) {
|
||||
segment.selected = true;
|
||||
}
|
||||
});
|
||||
if (list.id === campaign.list && !campaign.segment) {
|
||||
list.selected = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
campaign.csrfToken = req.csrfToken();
|
||||
campaign.listItems = listItems;
|
||||
campaign.useEditor = true;
|
||||
|
||||
campaign.showGeneral = req.query.tab === 'general' || !req.query.tab;
|
||||
campaign.showTemplate = req.query.tab === 'template';
|
||||
|
||||
res.render('campaigns/edit', campaign);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.update(req.body.id, req.body, (err, updated) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Campaign settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Campaign settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.id) {
|
||||
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
|
||||
} else {
|
||||
return res.redirect('/campaigns');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Campaign deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified campaign');
|
||||
}
|
||||
|
||||
return res.redirect('/campaigns');
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/view/:id', passport.csrfProtection, (req, res) => {
|
||||
campaigns.get(req.params.id, true, (err, campaign) => {
|
||||
if (err || !campaign) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find campaign with specified ID');
|
||||
return res.redirect('/campaigns');
|
||||
}
|
||||
|
||||
lists.get(campaign.list, (err, list) => {
|
||||
if (err || !campaign) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
return res.redirect('/campaigns');
|
||||
}
|
||||
|
||||
campaign.csrfToken = req.csrfToken();
|
||||
campaign.list = list;
|
||||
|
||||
campaign.isIdling = campaign.status === 1;
|
||||
campaign.isSending = campaign.status === 2;
|
||||
campaign.isFinished = campaign.status === 3;
|
||||
campaign.isPaused = campaign.status === 4;
|
||||
|
||||
campaign.openRate = campaign.delivered ? Math.round((campaign.opened / campaign.delivered) * 100) : 0;
|
||||
campaign.clicksRate = campaign.delivered ? Math.round((campaign.clicks / campaign.delivered) * 100) : 0;
|
||||
|
||||
res.render('campaigns/view', campaign);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Campaign deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified campaign');
|
||||
}
|
||||
|
||||
return res.redirect('/campaigns');
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.send(req.body.id, (err, scheduled) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (scheduled) {
|
||||
req.flash('success', 'Scheduled sending');
|
||||
} else {
|
||||
req.flash('info', 'Could not schedule sending');
|
||||
}
|
||||
|
||||
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.reset(req.body.id, (err, reset) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (reset) {
|
||||
req.flash('success', 'Sending reset');
|
||||
} else {
|
||||
req.flash('info', 'Could not reset sending');
|
||||
}
|
||||
|
||||
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/pause', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
campaigns.pause(req.body.id, (err, reset) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (reset) {
|
||||
req.flash('success', 'Sending paused');
|
||||
} else {
|
||||
req.flash('info', 'Could not pause sending');
|
||||
}
|
||||
|
||||
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
191
routes/fields.js
Normal file
191
routes/fields.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let lists = require('../lib/models/lists');
|
||||
let fields = require('../lib/models/fields');
|
||||
let tools = require('../lib/tools');
|
||||
let passport = require('../lib/passport');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('lists');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/:list', (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, rows) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
res.render('lists/fields/fields', {
|
||||
rows: rows.map(row => {
|
||||
row.index = ++index;
|
||||
row.type = fields.types[row.type];
|
||||
if (Array.isArray(row.options)) {
|
||||
row.options.forEach(option => {
|
||||
option.index = ++index;
|
||||
});
|
||||
}
|
||||
return row;
|
||||
}),
|
||||
list
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/create', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, rows) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.list = list;
|
||||
|
||||
if (data.type) {
|
||||
data['selected' + (data.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())] = true;
|
||||
}
|
||||
|
||||
if (!('visible' in data) && !data.name) {
|
||||
data.visible = true;
|
||||
}
|
||||
|
||||
data.groups = rows.filter(row => fields.grouped.indexOf(row.type) >= 0).map(row => {
|
||||
row.selected = Number(req.query.group) === row.id;
|
||||
return row;
|
||||
});
|
||||
|
||||
res.render('lists/fields/create', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
fields.create(req.params.list, req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create custom field');
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'Custom field created');
|
||||
res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/edit/:field', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
fields.get(req.params.field, (err, field) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!field) {
|
||||
req.flash('danger', 'Selected field not found');
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let data = {
|
||||
csrfToken: req.csrfToken(),
|
||||
field,
|
||||
list
|
||||
};
|
||||
|
||||
if (field.type) {
|
||||
data['selected' + (field.type || '').toString().trim().replace(/(?:^|\-)([a-z])/g, (m, c) => c.toUpperCase())] = true;
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, rows) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
data.groups = field.type === 'option' ? rows.filter(row => fields.grouped.indexOf(row.type) >= 0).map(row => {
|
||||
row.selected = Number(field.group) === row.id;
|
||||
return row;
|
||||
}) : false;
|
||||
|
||||
res.render('lists/fields/edit', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
fields.update(req.body.id, req.body, (err, updated) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Field settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Field settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.id) {
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list) + '/edit/' + encodeURIComponent(req.body.id));
|
||||
} else {
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
fields.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Custom field deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified field');
|
||||
}
|
||||
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
14
routes/index.js
Normal file
14
routes/index.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', (req, res) => {
|
||||
res.render('index', {
|
||||
indexPage: true,
|
||||
title: 'Self hosted email newsletter app'
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
47
routes/links.js
Normal file
47
routes/links.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
let links = require('../lib/models/links');
|
||||
|
||||
let log = require('npmlog');
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
|
||||
let trackImg = new Buffer('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', 'base64');
|
||||
|
||||
router.get('/:campaign/:list/:subscription', (req, res) => {
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'image/gif',
|
||||
'Content-Length': trackImg.length
|
||||
});
|
||||
|
||||
links.countOpen(req.ip, req.params.campaign, req.params.list, req.params.subscription, (err, opened) => {
|
||||
if (err) {
|
||||
log.error('Redirect', err.stack || err);
|
||||
}
|
||||
if (opened) {
|
||||
log.verbose('Redirect', 'First open for %s:%s:%s', req.params.campaign, req.params.list, req.params.subscription);
|
||||
}
|
||||
});
|
||||
|
||||
res.end(trackImg);
|
||||
});
|
||||
|
||||
router.get('/:campaign/:list/:subscription/:link', (req, res) => {
|
||||
links.resolve(req.params.campaign, req.params.link, (err, linkId, url) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
links.countClick(req.ip, req.params.campaign, req.params.list, req.params.subscription, linkId, (err, status) => {
|
||||
if (err) {
|
||||
log.error('Redirect', err.stack || err);
|
||||
}
|
||||
if (status) {
|
||||
log.verbose('Redirect', 'First click for %s:%s:%s (%s)', req.params.campaign, req.params.list, req.params.subscription, url);
|
||||
}
|
||||
});
|
||||
res.redirect(url);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
592
routes/lists.js
Normal file
592
routes/lists.js
Normal file
|
@ -0,0 +1,592 @@
|
|||
'use strict';
|
||||
|
||||
let passport = require('../lib/passport');
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let lists = require('../lib/models/lists');
|
||||
let subscriptions = require('../lib/models/subscriptions');
|
||||
let fields = require('../lib/models/fields');
|
||||
let tools = require('../lib/tools');
|
||||
let striptags = require('striptags');
|
||||
let htmlescape = require('escape-html');
|
||||
let multer = require('multer');
|
||||
let os = require('os');
|
||||
let humanize = require('humanize');
|
||||
let uploads = multer({
|
||||
dest: os.tmpdir()
|
||||
});
|
||||
let csvparse = require('csv-parse');
|
||||
let fs = require('fs');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('lists');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
let limit = 999999999;
|
||||
let start = 0;
|
||||
|
||||
lists.list(start, limit, (err, rows, total) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
res.render('lists/lists', {
|
||||
rows: rows.map((row, i) => {
|
||||
row.index = start + i + 1;
|
||||
row.description = striptags(row.description);
|
||||
return row;
|
||||
}),
|
||||
total
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/create', passport.csrfProtection, (req, res) => {
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
data.csrfToken = req.csrfToken();
|
||||
|
||||
res.render('lists/create', data);
|
||||
});
|
||||
|
||||
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.create(req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create list');
|
||||
return res.redirect('/lists/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'List created');
|
||||
res.redirect('/lists/view/' + id);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/edit/:id', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
list.csrfToken = req.csrfToken();
|
||||
res.render('lists/edit', list);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.update(req.body.id, req.body, (err, updated) => {
|
||||
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'List settings updated');
|
||||
} else {
|
||||
req.flash('info', 'List settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.id) {
|
||||
return res.redirect('/lists/edit/' + encodeURIComponent(req.body.id));
|
||||
} else {
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'List deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified list');
|
||||
}
|
||||
|
||||
return res.redirect('/lists');
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/ajax/:id', (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
return res.json({
|
||||
error: err && err.message || err || 'List not found',
|
||||
data: []
|
||||
});
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
let columns = ['#', 'email', 'first_name', 'last_name'].concat(fieldList.filter(field => field.visible).map(field => field.column)).concat('status');
|
||||
|
||||
subscriptions.filter(list.id, req.body, columns, req.query.segment, (err, data, total, filteredTotal) => {
|
||||
if (err) {
|
||||
return res.json({
|
||||
error: err.message || err,
|
||||
data: []
|
||||
});
|
||||
}
|
||||
|
||||
data.forEach(row => {
|
||||
row.subscriptionStatus = row.status === 1 ? true : false;
|
||||
row.customFields = fields.getRow(fieldList, row);
|
||||
});
|
||||
|
||||
let statuses = ['Unknown', 'Subscribed', 'Unsubscribed', 'Bounced', 'Complained'];
|
||||
|
||||
res.json({
|
||||
draw: req.body.draw,
|
||||
recordsTotal: total,
|
||||
recordsFiltered: filteredTotal,
|
||||
data: data.map((row, i) => [
|
||||
(Number(req.body.start) || 0) + 1 + i,
|
||||
htmlescape(row.email || ''),
|
||||
htmlescape(row.firstName || ''),
|
||||
htmlescape(row.lastName || '')
|
||||
].concat(fields.getRow(fieldList, row).map(cRow => {
|
||||
if (cRow.type === 'number') {
|
||||
return htmlescape(cRow.value && humanize.numberFormat(cRow.value, 0) || '');
|
||||
} else {
|
||||
return htmlescape(cRow.value || '');
|
||||
}
|
||||
})).concat(statuses[row.status]).concat('<span class="glyphicon glyphicon-wrench" aria-hidden="true"></span><a href="/lists/subscription/' + list.id + '/edit/' + row.cid + '">Edit</a>'))
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/view/:id', passport.csrfProtection, (req, res) => {
|
||||
if (Number(req.query.segment) === -1) {
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.id) + '/create');
|
||||
}
|
||||
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.listImports(list.id, (err, imports) => {
|
||||
if (err) {
|
||||
// not important, ignore
|
||||
imports = [];
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
list.imports = imports.map((entry, i) => {
|
||||
entry.index = i + 1;
|
||||
entry.processed = humanize.numberFormat(entry.processed, 0);
|
||||
entry.importType = entry.type === 1 ? 'Subscribe' : 'Unsubscribe';
|
||||
switch (entry.status) {
|
||||
case 0:
|
||||
entry.importStatus = 'Initializing';
|
||||
break;
|
||||
case 1:
|
||||
entry.importStatus = 'Initialized';
|
||||
break;
|
||||
case 2:
|
||||
entry.importStatus = 'Importing...';
|
||||
break;
|
||||
case 3:
|
||||
entry.importStatus = 'Finished';
|
||||
break;
|
||||
default:
|
||||
entry.importStatus = 'Errored' + (entry.error ? ' (' + entry.error + ')' : '');
|
||||
entry.error = true;
|
||||
}
|
||||
entry.created = entry.created && entry.created.toISOString();
|
||||
entry.finished = entry.finished && entry.finished.toISOString();
|
||||
return entry;
|
||||
});
|
||||
list.csrfToken = req.csrfToken();
|
||||
list.customFields = fieldList.filter(field => field.visible);
|
||||
list.customSort = list.customFields.length ? ',' + list.customFields.map(() => '0').join(',') : '';
|
||||
|
||||
list.showSubscriptions = req.query.tab === 'subscriptions' || !req.query.tab;
|
||||
list.showImports = req.query.tab === 'imports';
|
||||
|
||||
list.segments.forEach(segment => {
|
||||
if (segment.id === (Number(req.query.segment) || 0)) {
|
||||
segment.selected = true;
|
||||
list.useSegment = req.query.segment;
|
||||
list.segment = segment.id;
|
||||
}
|
||||
});
|
||||
|
||||
res.render('lists/view', list);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/subscription/:id/add', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
data.list = list;
|
||||
data.csrfToken = req.csrfToken();
|
||||
|
||||
data.customFields = fields.getRow(fieldList, data, false, true);
|
||||
data.useEditor = true;
|
||||
|
||||
res.render('lists/subscription/add', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/subscription/:id/edit/:cid', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.get(list.id, req.params.cid, (err, subscription) => {
|
||||
if (err || !subscription) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
|
||||
return res.redirect('/lists/view/' + req.params.id);
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
subscription.list = list;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
|
||||
subscription.customFields = fields.getRow(fieldList, subscription, false, true);
|
||||
subscription.useEditor = true;
|
||||
subscription.isSubscribed = subscription.status === 1;
|
||||
|
||||
res.render('lists/subscription/edit', subscription);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/add', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
subscriptions.insert(req.body.list, false, req.body, (err, entryId) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err || 'Could not add subscription');
|
||||
return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
if (entryId) {
|
||||
req.flash('success', req.body.email + ' was successfully added to your list');
|
||||
} else {
|
||||
req.flash('warning', req.body.email + ' was not added to your list');
|
||||
}
|
||||
|
||||
res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/add');
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.body.list, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.get(list.id, req.body.cid, (err, subscription) => {
|
||||
if (err || !subscription) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
|
||||
return res.redirect('/lists/view/' + list.id);
|
||||
}
|
||||
|
||||
subscriptions.unsubscribe(list.id, subscription.email, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err || 'Could not unsubscribe user');
|
||||
return res.redirect('/lists/subscription/' + list.id + '/edit/' + subscription.cid);
|
||||
}
|
||||
req.flash('success', subscription.email + ' was successfully subscribed from your list');
|
||||
res.redirect('/lists/view/' + list.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.body.list, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.delete(list.id, req.body.cid, (err, email) => {
|
||||
if (err || !email) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find subscriber with specified ID');
|
||||
return res.redirect('/lists/view/' + list.id);
|
||||
}
|
||||
|
||||
req.flash('success', email + ' was successfully removed from your list');
|
||||
res.redirect('/lists/view/' + list.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
subscriptions.update(req.body.list, req.body.cid, req.body, true, (err, updated) => {
|
||||
|
||||
if (err) {
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
req.flash('danger', 'Another subscriber with email address ' + req.body.email + ' already exists');
|
||||
return res.redirect('/lists/subscription/' + encodeURIComponent(req.body.list) + '/edit/' + req.body.cid);
|
||||
} else {
|
||||
req.flash('danger', err.message || err);
|
||||
}
|
||||
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Subscription settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Subscription settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.list) {
|
||||
return res.redirect('/lists/view/' + encodeURIComponent(req.body.list));
|
||||
} else {
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/subscription/:id/import', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
if (!('delimiter' in data)) {
|
||||
data.delimiter = ',';
|
||||
}
|
||||
|
||||
data.list = list;
|
||||
data.csrfToken = req.csrfToken();
|
||||
|
||||
res.render('lists/subscription/import', data);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/subscription/:id/import/:importId', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.id, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.getImport(req.params.id, req.params.importId, (err, data) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
data.list = list;
|
||||
data.csrfToken = req.csrfToken();
|
||||
|
||||
data.customFields = fields.getRow(fieldList, data);
|
||||
|
||||
res.render('lists/subscription/import-preview', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/import', uploads.single('listimport'), passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.body.list, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
let delimiter = (req.body.delimiter || '').trim().charAt(0) || ',';
|
||||
|
||||
getPreview(req.file.path, req.file.size, delimiter, (err, rows) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err || 'Could not process CSV');
|
||||
return res.redirect('/lists');
|
||||
} else {
|
||||
|
||||
subscriptions.createImport(list.id, req.body.type === 'subscribed' ? 1 : 2, req.file.path, req.file.size, delimiter, {
|
||||
columns: rows[0],
|
||||
example: rows[1] || []
|
||||
}, (err, importId) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create importer');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
return res.redirect('/lists/subscription/' + list.id + '/import/' + importId);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getPreview(path, size, delimiter, callback) {
|
||||
delimiter = (delimiter || '').trim().charAt(0) || ',';
|
||||
size = Number(size);
|
||||
|
||||
fs.open(path, 'r', (err, fd) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let bufLen = size;
|
||||
let maxReadSize = 10 * 1024;
|
||||
|
||||
if (size > maxReadSize) {
|
||||
bufLen = maxReadSize;
|
||||
}
|
||||
|
||||
let buffer = new Buffer(bufLen);
|
||||
fs.read(fd, buffer, 0, buffer.length, 0, (err, bytesRead, buffer) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let input = buffer.toString().trim();
|
||||
|
||||
|
||||
if (size !== bufLen) {
|
||||
// remove last incomplete line
|
||||
input = input.split(/\r?\n/);
|
||||
input.pop();
|
||||
input = input.join('\n');
|
||||
}
|
||||
|
||||
csvparse(input, {
|
||||
comment: '#',
|
||||
delimiter
|
||||
}, (err, data) => {
|
||||
fs.close(fd, () => {
|
||||
// just ignore
|
||||
});
|
||||
if (!data || !data.length) {
|
||||
return callback(null, new Error('Empty file'));
|
||||
}
|
||||
callback(err, data);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
router.post('/subscription/import-confirm', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.body.list, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.getImport(list.id, req.body.import, (err, data) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
let allowedColumns = ['email', 'first_name', 'last_name'];
|
||||
fieldList.forEach(field => {
|
||||
if (field.column) {
|
||||
allowedColumns.push(field.column);
|
||||
}
|
||||
if (field.options) {
|
||||
field.options.forEach(subField => {
|
||||
if (subField.column) {
|
||||
allowedColumns.push(subField.column);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
data.mapping.mapping = {};
|
||||
data.mapping.columns.forEach((column, i) => {
|
||||
let colIndex = allowedColumns.indexOf(req.body['column-' + i]);
|
||||
if (colIndex >= 0) {
|
||||
data.mapping.mapping[allowedColumns[colIndex]] = i;
|
||||
}
|
||||
});
|
||||
|
||||
subscriptions.updateImport(list.id, req.body.import, {
|
||||
status: 1,
|
||||
mapping: JSON.stringify(data.mapping)
|
||||
}, (err, importer) => {
|
||||
if (err || !importer) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
req.flash('success', 'Import started');
|
||||
res.redirect('/lists/view/' + list.id + '?tab=imports');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/subscription/import-restart', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.body.list, (err, list) => {
|
||||
if (err || !list) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find list with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
subscriptions.updateImport(list.id, req.body.import, {
|
||||
status: 1,
|
||||
error: null,
|
||||
finished: null,
|
||||
processed: 0
|
||||
}, (err, importer) => {
|
||||
if (err || !importer) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find import data with specified ID');
|
||||
return res.redirect('/lists');
|
||||
}
|
||||
|
||||
req.flash('success', 'Import restarted');
|
||||
res.redirect('/lists/view/' + list.id + '?tab=imports');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
435
routes/segments.js
Normal file
435
routes/segments.js
Normal file
|
@ -0,0 +1,435 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let passport = require('../lib/passport');
|
||||
let lists = require('../lib/models/lists');
|
||||
let segments = require('../lib/models/segments');
|
||||
let tools = require('../lib/tools');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('lists');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/:list', (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.list(list.id, (err, rows) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/fields/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
res.render('lists/segments/segments', {
|
||||
rows: rows.map(row => {
|
||||
row.index = ++index;
|
||||
row.type = row.type === 1 ? 'ALL' : 'ANY';
|
||||
return row;
|
||||
}),
|
||||
list
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/create', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
let segment = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
segment.csrfToken = req.csrfToken();
|
||||
segment.list = list;
|
||||
|
||||
switch (Number(segment.type) || 0) {
|
||||
case 1:
|
||||
segment.matchAll = true;
|
||||
break;
|
||||
case 2:
|
||||
segment.matchAny = true;
|
||||
break;
|
||||
}
|
||||
|
||||
res.render('lists/segments/create', segment);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
segments.create(req.params.list, req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create segment');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'Segment created');
|
||||
res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + id);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/view/:id', (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.id, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segment.list = list;
|
||||
segment.rules.forEach((rule, i) => {
|
||||
rule.index = i + 1;
|
||||
});
|
||||
|
||||
switch (Number(segment.type) || 0) {
|
||||
case 1:
|
||||
segment.type = 'ALL';
|
||||
break;
|
||||
case 2:
|
||||
segment.type = 'ANY';
|
||||
break;
|
||||
}
|
||||
|
||||
segments.subscribers(req.params.id, false, (err, subscribers) => {
|
||||
if (err) {
|
||||
// ignore
|
||||
}
|
||||
segment.subscribers = subscribers || 0;
|
||||
|
||||
res.render('lists/segments/view', segment);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/edit/:segment', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.segment, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
segment.csrfToken = req.csrfToken();
|
||||
segment.list = list;
|
||||
|
||||
switch (Number(segment.type) || 0) {
|
||||
case 1:
|
||||
segment.matchAll = true;
|
||||
break;
|
||||
case 2:
|
||||
segment.matchAny = true;
|
||||
break;
|
||||
}
|
||||
|
||||
res.render('lists/segments/edit', segment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
segments.update(req.body.id, req.body, (err, updated) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Segment settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Segment settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.id) {
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.body.id));
|
||||
} else {
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
segments.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Segment deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified segment');
|
||||
}
|
||||
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/rules/:segment/create', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.segment, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
segment.csrfToken = req.csrfToken();
|
||||
segment.list = list;
|
||||
|
||||
res.render('lists/segments/rule-create', segment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/rules/:segment/next', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.segment, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let column = segment.columns.filter(column => column.column === req.body.column).pop();
|
||||
if (!column) {
|
||||
req.flash('danger', 'Invalid rule type');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/configure?' + tools.queryParams(req.body));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/rules/:segment/configure', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.segment, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let column = segment.columns.filter(column => column.column === req.query.column).pop();
|
||||
if (!column) {
|
||||
req.flash('danger', 'Invalid rule type');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + segment.id + '/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
segment.csrfToken = req.csrfToken();
|
||||
|
||||
segment.value = data;
|
||||
segment.list = list;
|
||||
segment.column = column;
|
||||
segment['columnType' + column.type.replace(/^[a-z]/, c => c.toUpperCase())] = true;
|
||||
|
||||
segment.useEditor = true;
|
||||
|
||||
res.render('lists/segments/rule-configure', segment);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/rules/:segment/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.createRule(req.params.segment, req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create rule');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/rules/' + encodeURIComponent(req.params.segment) + '/configure?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'Rule created');
|
||||
res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:list/rules/:segment/edit/:rule', passport.csrfProtection, (req, res) => {
|
||||
lists.get(req.params.list, (err, list) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!list) {
|
||||
req.flash('danger', 'Selected list ID not found');
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
segments.get(req.params.segment, (err, segment) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
segments.getRule(req.params.rule, (err, rule) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
if (!segment) {
|
||||
req.flash('danger', 'Selected segment not found');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
|
||||
let column = segment.columns.filter(column => column.column === rule.column).pop();
|
||||
if (!column) {
|
||||
req.flash('danger', 'Invalid rule type');
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + segment.id);
|
||||
}
|
||||
|
||||
rule.csrfToken = req.csrfToken();
|
||||
rule.list = list;
|
||||
rule.segment = segment;
|
||||
rule.column = column;
|
||||
rule['columnType' + column.type.replace(/^[a-z]/, c => c.toUpperCase())] = true;
|
||||
|
||||
rule.useEditor = true;
|
||||
|
||||
res.render('lists/segments/rule-edit', rule);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/rules/:segment/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
segments.updateRule(req.body.id, req.body, (err, updated) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Rule settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Rule settings not updated');
|
||||
}
|
||||
|
||||
if (req.params.segment) {
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment));
|
||||
} else {
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:list/rules/:segment/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
segments.deleteRule(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Rule deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified rule');
|
||||
}
|
||||
|
||||
return res.redirect('/segments/' + encodeURIComponent(req.params.list) + '/view/' + encodeURIComponent(req.params.segment));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
155
routes/settings.js
Normal file
155
routes/settings.js
Normal file
|
@ -0,0 +1,155 @@
|
|||
'use strict';
|
||||
|
||||
let passport = require('../lib/passport');
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let tools = require('../lib/tools');
|
||||
let nodemailer = require('nodemailer');
|
||||
let mailer = require('../lib/mailer');
|
||||
|
||||
let settings = require('../lib/models/settings');
|
||||
|
||||
let allowedKeys = ['service_url', 'smtp_hostname', 'smtp_port', 'smtp_encryption', 'smtp_user', 'smtp_pass', 'admin_email', 'smtp_log', 'smtp_max_connections', 'smtp_max_messages', 'default_from', 'default_address', 'default_subject', 'default_homepage', 'default_postaddress', 'default_sender'];
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('/settings');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/', passport.csrfProtection, (req, res, next) => {
|
||||
settings.list((err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
configItems.smtpEncryption = [{
|
||||
checked: configItems.smtpEncryption === 'TLS' || !configItems.smtpEncryption,
|
||||
key: 'TLS',
|
||||
value: 'Use TLS',
|
||||
description: 'usually selected for port 465'
|
||||
}, {
|
||||
checked: configItems.smtpEncryption === 'STARTTLS',
|
||||
key: 'STARTTLS',
|
||||
value: 'Use STARTTLS',
|
||||
description: 'usually selected for port 587 and 25'
|
||||
}, {
|
||||
checked: configItems.smtpEncryption === 'NONE',
|
||||
key: 'NONE',
|
||||
value: 'Do not use encryption'
|
||||
}];
|
||||
|
||||
configItems.csrfToken = req.csrfToken();
|
||||
res.render('settings', configItems);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/update', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
|
||||
let data = tools.convertKeys(req.body);
|
||||
|
||||
tools.validateEmail(data.adminEmail, false, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
return res.redirect('/settings');
|
||||
}
|
||||
|
||||
let keys = [];
|
||||
let values = [];
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
let value = data[key].trim();
|
||||
key = tools.toDbKey(key);
|
||||
if (allowedKeys.indexOf(key) >= 0) {
|
||||
keys.push(key);
|
||||
values.push(value);
|
||||
}
|
||||
});
|
||||
// checkbox is not included in value listing if left unchecked
|
||||
if (keys.indexOf('smtp_log') < 0) {
|
||||
keys.push('smtp_log');
|
||||
values.push('');
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
let storeSettings = () => {
|
||||
if (i >= keys.length) {
|
||||
mailer.update();
|
||||
req.flash('success', 'Settings updated');
|
||||
return res.redirect('/settings');
|
||||
}
|
||||
let key = keys[i];
|
||||
let value = values[i];
|
||||
i++;
|
||||
|
||||
settings.set(key, value, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
return res.redirect('/settings');
|
||||
}
|
||||
storeSettings();
|
||||
});
|
||||
};
|
||||
|
||||
storeSettings();
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/smtp-verify', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
settings.list((err, configItems) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/settings');
|
||||
}
|
||||
|
||||
let transport = nodemailer.createTransport({
|
||||
host: configItems.smtpHostname,
|
||||
port: Number(configItems.smtpPort) || false,
|
||||
secure: configItems.smtpEncryption === 'TLS',
|
||||
ignoreTLS: configItems.smtpEncryption === 'NONE',
|
||||
auth: {
|
||||
user: configItems.smtpUser,
|
||||
pass: configItems.smtpPass
|
||||
}
|
||||
});
|
||||
|
||||
transport.verify(err => {
|
||||
if (err) {
|
||||
let message = '';
|
||||
switch (err.code) {
|
||||
case 'ECONNREFUSED':
|
||||
message = 'Connection refused, check hostname and port.';
|
||||
break;
|
||||
case 'ETIMEDOUT':
|
||||
if ((err.message || '').indexOf('Greeting never received') === 0) {
|
||||
if (configItems.smtpEncryption !== 'TLS') {
|
||||
message = 'Did not receive greeting message from server. This might happen when connecting to a TLS port without using TLS.';
|
||||
} else {
|
||||
message = 'Did not receive greeting message from server.';
|
||||
}
|
||||
} else {
|
||||
message = 'Connection timed out. Check your firewall settings, destination port is probably blocked.';
|
||||
}
|
||||
break;
|
||||
case 'EAUTH':
|
||||
if (/\b5\.7\.0\b/.test(err.message) && configItems.smtpEncryption !== 'STARTTLS') {
|
||||
message = 'Authentication not accepted, server expects STARTTLS to be used.';
|
||||
} else {
|
||||
message = 'Authentication failed, check username and password.';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
req.flash('warning', (message || 'Failed SMTP verification.') + (err.response ? ' Server responded with: "' + err.response + '"' : ''));
|
||||
} else {
|
||||
req.flash('info', 'SMTP settings verified, ready to send some mail!');
|
||||
}
|
||||
return res.redirect('/settings');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
344
routes/subscription.js
Normal file
344
routes/subscription.js
Normal file
|
@ -0,0 +1,344 @@
|
|||
'use strict';
|
||||
|
||||
let log = require('npmlog');
|
||||
|
||||
let tools = require('../lib/tools');
|
||||
let mailer = require('../lib/mailer');
|
||||
let passport = require('../lib/passport');
|
||||
let express = require('express');
|
||||
let urllib = require('url');
|
||||
let router = new express.Router();
|
||||
let lists = require('../lib/models/lists');
|
||||
let fields = require('../lib/models/fields');
|
||||
let subscriptions = require('../lib/models/subscriptions');
|
||||
let settings = require('../lib/models/settings');
|
||||
|
||||
router.get('/subscribe/:cid', (req, res, next) => {
|
||||
subscriptions.subscribe(req.params.cid, req.ip, (err, subscription) => {
|
||||
if (!err && !subscription) {
|
||||
err = new Error('Selected subscription not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
lists.get(subscription.list, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
settings.list(['defaultHomepage', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('subscription/subscribed', {
|
||||
title: list.name,
|
||||
layout: 'subscription/layout',
|
||||
homepage: configItems.defaultHomepage || configItems.serviceUrl
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:cid', passport.csrfProtection, (req, res, next) => {
|
||||
lists.getByCid(req.params.cid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
data.layout = 'subscription/layout';
|
||||
data.title = list.name;
|
||||
data.cid = list.cid;
|
||||
data.csrfToken = req.csrfToken();
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err && !fieldList) {
|
||||
fieldList = [];
|
||||
}
|
||||
|
||||
data.customFields = fields.getRow(fieldList, data);
|
||||
data.useEditor = true;
|
||||
|
||||
res.render('subscription/subscribe', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:cid/confirm-notice', (req, res, next) => {
|
||||
lists.getByCid(req.params.cid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
settings.list(['defaultHomepage', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('subscription/confirm-notice', {
|
||||
title: list.name,
|
||||
layout: 'subscription/layout',
|
||||
homepage: configItems.defaultHomepage || configItems.serviceUrl
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:cid/updated-notice', (req, res, next) => {
|
||||
lists.getByCid(req.params.cid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
settings.list(['defaultHomepage', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('subscription/updated-notice', {
|
||||
title: list.name,
|
||||
layout: 'subscription/layout',
|
||||
homepage: configItems.defaultHomepage || configItems.serviceUrl
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:cid/unsubscribe-notice', (req, res, next) => {
|
||||
lists.getByCid(req.params.cid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
settings.list(['defaultHomepage', 'serviceUrl'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
res.render('subscription/unsubscribe-notice', {
|
||||
title: list.name,
|
||||
layout: 'subscription/layout',
|
||||
homepage: configItems.defaultHomepage || configItems.serviceUrl
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:cid/subscribe', passport.parseForm, passport.csrfProtection, (req, res, next) => {
|
||||
let email = (req.body.email || '').toString().trim();
|
||||
|
||||
if (!email) {
|
||||
req.flash('danger', 'Email address not set');
|
||||
return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
lists.getByCid(req.params.cid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
let data = {};
|
||||
Object.keys(req.body).forEach(key => {
|
||||
if (key !== 'email' && key.charAt(0) !== '_') {
|
||||
data[key] = (req.body[key] || '').toString().trim();
|
||||
}
|
||||
});
|
||||
data = tools.convertKeys(data);
|
||||
|
||||
subscriptions.addConfirmation(list.id, email, data, (err, confirmCid) => {
|
||||
if (!err && !confirmCid) {
|
||||
err = new Error('Could not store confirmation data');
|
||||
}
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/subscription/' + encodeURIComponent(req.params.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
|
||||
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'
|
||||
}, {
|
||||
template: 'emails/confirm-mail.hbs',
|
||||
data: {
|
||||
title: list.name,
|
||||
contactAddress: configItems.defaultAddress,
|
||||
confirmUrl: urllib.resolve(configItems.serviceUrl, '/subscription/subscribe/' + confirmCid)
|
||||
}
|
||||
}, err => {
|
||||
if (err) {
|
||||
log.error('Subscription', err.stack);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:lcid/manage/:ucid', passport.csrfProtection, (req, res, next) => {
|
||||
lists.getByCid(req.params.lcid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
fields.list(list.id, (err, fieldList) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
subscriptions.get(list.id, req.params.ucid, (err, subscription) => {
|
||||
if (!err && !subscription) {
|
||||
err = new Error('Subscription not found from this list');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
subscription.lcid = req.params.lcid;
|
||||
subscription.title = list.name;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
subscription.layout = 'subscription/layout';
|
||||
|
||||
subscription.customFields = fields.getRow(fieldList, subscription);
|
||||
|
||||
subscription.useEditor = true;
|
||||
|
||||
res.render('subscription/manage', subscription);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:lcid/manage', passport.parseForm, passport.csrfProtection, (req, res, next) => {
|
||||
lists.getByCid(req.params.lcid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
subscriptions.update(list.id, req.body.cid, req.body, false, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/subscription/' + encodeURIComponent(req.params.lcid) + '/manage/' + encodeURIComponent(req.body.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
res.redirect('/subscription/' + req.params.lcid + '/updated-notice');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:lcid/unsubscribe/:ucid', passport.csrfProtection, (req, res, next) => {
|
||||
lists.getByCid(req.params.lcid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
subscriptions.get(list.id, req.params.ucid, (err, subscription) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Subscription not found from this list');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
subscription.lcid = req.params.lcid;
|
||||
subscription.title = list.name;
|
||||
subscription.csrfToken = req.csrfToken();
|
||||
subscription.layout = 'subscription/layout';
|
||||
subscription.autosubmit = !!req.query.auto;
|
||||
subscription.campaign = req.query.c;
|
||||
res.render('subscription/unsubscribe', subscription);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:lcid/unsubscribe', passport.parseForm, passport.csrfProtection, (req, res, next) => {
|
||||
lists.getByCid(req.params.lcid, (err, list) => {
|
||||
if (!err && !list) {
|
||||
err = new Error('Selected list not found');
|
||||
err.status = 404;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
let email = req.body.email;
|
||||
|
||||
subscriptions.unsubscribe(list.id, email, req.body.campaign, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/subscription/' + encodeURIComponent(req.params.lcid) + '/unsubscribe/' + encodeURIComponent(req.body.cid) + '?' + tools.queryParams(req.body));
|
||||
}
|
||||
res.redirect('/subscription/' + req.params.lcid + '/unsubscribe-notice');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
128
routes/templates.js
Normal file
128
routes/templates.js
Normal file
|
@ -0,0 +1,128 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let templates = require('../lib/models/templates');
|
||||
let settings = require('../lib/models/settings');
|
||||
let tools = require('../lib/tools');
|
||||
let striptags = require('striptags');
|
||||
let passport = require('../lib/passport');
|
||||
let mailer = require('../lib/mailer');
|
||||
|
||||
router.all('/*', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
res.setSelectedMenu('templates');
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
let limit = 999999999;
|
||||
let start = 0;
|
||||
|
||||
templates.list(start, limit, (err, rows, total) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
res.render('templates/templates', {
|
||||
rows: rows.map((row, i) => {
|
||||
row.index = start + i + 1;
|
||||
row.description = striptags(row.description);
|
||||
return row;
|
||||
}),
|
||||
total
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/create', passport.csrfProtection, (req, res, next) => {
|
||||
let data = tools.convertKeys(req.query, {
|
||||
skip: ['layout']
|
||||
});
|
||||
|
||||
data.csrfToken = req.csrfToken();
|
||||
data.useEditor = true;
|
||||
|
||||
settings.list(['defaultPostaddress', 'defaultSender'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
mailer.getTemplate('emails/stationery-html.hbs', (err, rendererHtml) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
mailer.getTemplate('emails/stationery-text.hbs', (err, rendererText) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
data.html = data.html || rendererHtml(configItems);
|
||||
data.text = data.text || rendererText(configItems);
|
||||
res.render('templates/create', data);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/create', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
templates.create(req.body, (err, id) => {
|
||||
if (err || !id) {
|
||||
req.flash('danger', err && err.message || err || 'Could not create template');
|
||||
return res.redirect('/templates/create?' + tools.queryParams(req.body));
|
||||
}
|
||||
req.flash('success', 'Template created');
|
||||
res.redirect('/templates');
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/edit/:id', passport.csrfProtection, (req, res) => {
|
||||
templates.get(req.params.id, (err, template) => {
|
||||
if (err || !template) {
|
||||
req.flash('danger', err && err.message || err || 'Could not find template with specified ID');
|
||||
return res.redirect('/templates');
|
||||
}
|
||||
template.csrfToken = req.csrfToken();
|
||||
template.useEditor = true;
|
||||
res.render('templates/edit', template);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/edit', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
templates.update(req.body.id, req.body, (err, updated) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (updated) {
|
||||
req.flash('success', 'Template settings updated');
|
||||
} else {
|
||||
req.flash('info', 'Template settings not updated');
|
||||
}
|
||||
|
||||
if (req.body.id) {
|
||||
return res.redirect('/templates/edit/' + encodeURIComponent(req.body.id));
|
||||
} else {
|
||||
return res.redirect('/templates');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
templates.delete(req.body.id, (err, deleted) => {
|
||||
if (err) {
|
||||
req.flash('danger', err && err.message || err);
|
||||
} else if (deleted) {
|
||||
req.flash('success', 'Template deleted');
|
||||
} else {
|
||||
req.flash('info', 'Could not delete specified template');
|
||||
}
|
||||
|
||||
return res.redirect('/templates');
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
99
routes/users.js
Normal file
99
routes/users.js
Normal file
|
@ -0,0 +1,99 @@
|
|||
'use strict';
|
||||
|
||||
let passport = require('../lib/passport');
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let users = require('../lib/models/users');
|
||||
|
||||
router.get('/logout', (req, res) => passport.logout(req, res));
|
||||
|
||||
router.post('/login', passport.parseForm, (req, res, next) => passport.login(req, res, next));
|
||||
router.get('/login', (req, res) => {
|
||||
res.render('users/login', {
|
||||
next: req.query.next
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/forgot', passport.csrfProtection, (req, res) => {
|
||||
res.render('users/forgot', {
|
||||
csrfToken: req.csrfToken()
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/forgot', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
users.sendReset(req.body.username, err => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/users/forgot');
|
||||
} else {
|
||||
req.flash('success', 'An email with password reset instructions has been sent to your email address, if it exists on our system.');
|
||||
}
|
||||
return res.redirect('/users/login');
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/reset', passport.csrfProtection, (req, res) => {
|
||||
users.checkResetToken(req.query.username, req.query.token, (err, status) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/users/login');
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
req.flash('danger', 'Unknown or expired reset token');
|
||||
return res.redirect('/users/login');
|
||||
}
|
||||
|
||||
res.render('users/reset', {
|
||||
csrfToken: req.csrfToken(),
|
||||
username: req.query.username,
|
||||
resetToken: req.query.token
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
users.resetPassword(req.body, (err, status) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
return res.redirect('/users/reset?username=' + encodeURIComponent(req.body.username) + '&token=' + encodeURIComponent(req.body['reset-token']));
|
||||
} else if (!status) {
|
||||
req.flash('danger', 'Unknown or expired reset token');
|
||||
} else {
|
||||
req.flash('success', 'Your password has been changed successfully');
|
||||
}
|
||||
|
||||
return res.redirect('/users/login');
|
||||
});
|
||||
});
|
||||
|
||||
router.all('/account', (req, res, next) => {
|
||||
if (!req.user) {
|
||||
req.flash('danger', 'Need to be logged in to access restricted content');
|
||||
return res.redirect('/users/login?next=' + encodeURIComponent(req.originalUrl));
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
router.get('/account', passport.csrfProtection, (req, res) => {
|
||||
let data = {
|
||||
csrfToken: req.csrfToken(),
|
||||
email: req.user.email
|
||||
};
|
||||
res.render('users/account', data);
|
||||
});
|
||||
|
||||
router.post('/account', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||
users.update(Number(req.user.id), req.body, (err, success) => {
|
||||
if (err) {
|
||||
req.flash('danger', err.message || err);
|
||||
} else if (success) {
|
||||
req.flash('success', 'Account information updated');
|
||||
} else {
|
||||
req.flash('info', 'Account information not updated');
|
||||
}
|
||||
return res.redirect('/users/account');
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
363
routes/webhooks.js
Normal file
363
routes/webhooks.js
Normal file
|
@ -0,0 +1,363 @@
|
|||
'use strict';
|
||||
|
||||
let express = require('express');
|
||||
let router = new express.Router();
|
||||
let request = require('request');
|
||||
let campaigns = require('../lib/models/campaigns');
|
||||
let subscriptions = require('../lib/models/subscriptions');
|
||||
let db = require('../lib/db');
|
||||
let log = require('npmlog');
|
||||
let multer = require('multer');
|
||||
let uploads = multer();
|
||||
|
||||
router.post('/aws', (req, res, next) => {
|
||||
if (typeof req.body === 'string') {
|
||||
try {
|
||||
req.body = JSON.parse(req.body);
|
||||
} catch (E) {
|
||||
return next(new Error('Could not parse input'));
|
||||
}
|
||||
}
|
||||
|
||||
switch (req.body.Type) {
|
||||
|
||||
case 'SubscriptionConfirmation':
|
||||
if (req.body.SubscribeURL) {
|
||||
request(req.body.SubscribeURL, () => false);
|
||||
break;
|
||||
} else {
|
||||
let err = new Error('SubscribeURL not set');
|
||||
err.status = 400;
|
||||
return next(err);
|
||||
}
|
||||
|
||||
case 'Notification':
|
||||
if (req.body.Message) {
|
||||
if (typeof req.body.Message === 'string') {
|
||||
try {
|
||||
req.body.Message = JSON.parse(req.body.Message);
|
||||
} catch (E) {
|
||||
return next('Could not parse Message object');
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.Message.mail && req.body.Message.mail.messageId) {
|
||||
campaigns.findMail(req.body.Message.mail.messageId, (err, message) => {
|
||||
if (err || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (req.body.Message.notificationType) {
|
||||
case 'Bounce':
|
||||
updateMessage(message, 'bounced', ['Undetermined', 'Permanent'].indexOf(req.body.Message.bounce.bounceType) >= 0, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('AWS', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('AWS', 'Marked message %s as bounced', req.body.Message.mail.messageId);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'Complaint':
|
||||
if (req.body.Message.complaint) {
|
||||
updateMessage(message, 'complained', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('AWS', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('AWS', 'Marked message %s as complaint', req.body.Message.mail.messageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/sparkpost', (req, res, next) => {
|
||||
let events = [].concat(req.body || []);
|
||||
let pos = 0;
|
||||
|
||||
let processEvents = () => {
|
||||
if (pos >= events.length) {
|
||||
return res.json({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
let curEvent = events[pos++];
|
||||
|
||||
let msys = curEvent && curEvent.msys;
|
||||
let evt;
|
||||
|
||||
if (msys && msys.message_event) {
|
||||
evt = msys.message_event;
|
||||
} else if (msys && msys.unsubscribe_event) {
|
||||
evt = msys.unsubscribe_event;
|
||||
}
|
||||
|
||||
if (!evt) {
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
getMessage(evt.campaign_id, (err, message) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
switch (evt.type) {
|
||||
case 'bounce':
|
||||
// https://support.sparkpost.com/customer/portal/articles/1929896
|
||||
return updateMessage(message, 'bounced', [1, 10, 25, 30, 50].indexOf(Number(evt.bounce_class)) >= 0, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sparkpost', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sparkpost', 'Marked message %s as bounced', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
case 'spam_complaint':
|
||||
return updateMessage(message, 'complained', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sparkpost', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sparkpost', 'Marked message %s as complaint', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
case 'link_unsubscribe':
|
||||
return updateMessage(message, 'unsubscribed', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sparkpost', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sparkpost', 'Marked message %s as unsubscribed', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
default:
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
processEvents();
|
||||
});
|
||||
|
||||
router.post('/sendgrid', (req, res, next) => {
|
||||
console.log(require('util').inspect(req.body, false, 22)); // eslint-disable-line
|
||||
|
||||
let events = [].concat(req.body || []);
|
||||
let pos = 0;
|
||||
|
||||
let processEvents = () => {
|
||||
if (pos >= events.length) {
|
||||
return res.json({
|
||||
success: true
|
||||
});
|
||||
}
|
||||
let evt = events[pos++];
|
||||
|
||||
if (!evt) {
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
getMessage(evt.campaign_id, (err, message) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
switch (evt.event) {
|
||||
case 'bounce':
|
||||
// https://support.sparkpost.com/customer/portal/articles/1929896
|
||||
return updateMessage(message, 'bounced', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sendgrid', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sendgrid', 'Marked message %s as bounced', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
case 'spamreport':
|
||||
return updateMessage(message, 'complained', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sendgrid', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sendgrid', 'Marked message %s as complaint', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
case 'group_unsubscribe':
|
||||
case 'unsubscribe':
|
||||
return updateMessage(message, 'unsubscribed', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Sendgrid', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Sendgrid', 'Marked message %s as unsubscribed', evt.campaign_id);
|
||||
}
|
||||
return processEvents();
|
||||
});
|
||||
default:
|
||||
return processEvents();
|
||||
}
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
processEvents();
|
||||
});
|
||||
|
||||
router.post('/mailgun', uploads.any(), (req, res) => {
|
||||
let evt = req.body;
|
||||
getMessage([].concat(evt && evt.campaign_id || []).shift(), (err, message) => {
|
||||
if (err || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (evt.event) {
|
||||
case 'bounced':
|
||||
return updateMessage(message, 'bounced', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Mailgun', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Mailgun', 'Marked message %s as bounced', evt.campaign_id);
|
||||
}
|
||||
});
|
||||
case 'complained':
|
||||
return updateMessage(message, 'complained', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Mailgun', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Mailgun', 'Marked message %s as complaint', evt.campaign_id);
|
||||
}
|
||||
});
|
||||
case 'unsubscribed':
|
||||
return updateMessage(message, 'unsubscribed', true, (err, updated) => {
|
||||
if (err) {
|
||||
log.error('Mailgun', 'Failed updating message: %s', err.stack);
|
||||
} else if (updated) {
|
||||
log.verbose('Mailgun', 'Marked message %s as unsubscribed', evt.campaign_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
function getMessage(messageHeader, callback) {
|
||||
if (!messageHeader) {
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
let parts = messageHeader.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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function 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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue