diff --git a/server/app-builder.js b/server/app-builder.js index a9243f4a..2733d8eb 100644 --- a/server/app-builder.js +++ b/server/app-builder.js @@ -348,7 +348,7 @@ function createApp(appType) { data: [] }; - return status(err.status || 500).json(resp); + return res.status(err.status || 500).json(resp); } else { if (err instanceof interoperableErrors.NotLoggedInError) { diff --git a/server/lib/privilege-helpers.js b/server/lib/privilege-helpers.js index 14a2f00d..d1f4671c 100644 --- a/server/lib/privilege-helpers.js +++ b/server/lib/privilege-helpers.js @@ -8,6 +8,10 @@ const fs = require('fs-extra-promise'); const tryRequire = require('try-require'); const posix = tryRequire('posix'); +// process.getuid and process.getgid are not supported on Windows +process.getuid = process.getuid || (() => 100); +process.getgid = process.getuid || (() => 100); + function _getConfigUidGid(userKey, groupKey, defaultUid, defaultGid) { let uid = defaultUid; let gid = defaultGid; diff --git a/server/lib/template-sender.js b/server/lib/template-sender.js new file mode 100644 index 00000000..40ccd0b8 --- /dev/null +++ b/server/lib/template-sender.js @@ -0,0 +1,75 @@ +'use strict'; + +const contextHelpers = require('./context-helpers'); +const mailers = require('./mailers'); +const templates = require('../models/templates'); + +class TemplateSender { + constructor({ templateId, maxMails = 100 } = {}) { + if (!templateId) { + throw new Error('Cannot create template sender without templateId'); + } + + this.templateId = templateId; + this.maxMails = maxMails; + } + + async send(options) { + this._validateMailOptions(options); + + const [mailer, template] = await Promise.all([ + mailers.getOrCreateMailer(), + templates.getById( + contextHelpers.getAdminContext(), + this.templateId, + false + ) + ]); + + const html = this._substituteVariables( + template.html, + options.variables + ); + return mailer.sendTransactionalMail( + { + to: options.email, + subject: options.subject + }, + { + html: { template: html }, + locale: options.locale + } + ); + } + + _validateMailOptions(options) { + let { email, locale } = options; + + if (!email || email.length === 0) { + throw new Error('Missing email'); + } + if (typeof email === 'string') { + email = email.split(','); + } + if (email.length > this.maxMails) { + throw new Error( + `Cannot send more than ${this.maxMails} emails at once` + ); + } + if (!locale) { + throw new Error('Missing locale'); + } + } + + _substituteVariables(html, variables) { + if (!variables) return html; + return Object.keys(variables).reduce((res, key) => { + return res.replace( + new RegExp(`\\[${key}\\]`, 'gmi'), + variables[key] + ); + }, html); + } +} + +module.exports = TemplateSender; diff --git a/server/routes/api.js b/server/routes/api.js index 2dc81884..9fcc2863 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -16,6 +16,7 @@ const contextHelpers = require('../lib/context-helpers'); const shares = require('../models/shares'); const slugify = require('slugify'); const passport = require('../lib/passport'); +const TemplateSender = require('../lib/template-sender'); const campaigns = require('../models/campaigns'); class APIError extends Error { @@ -285,5 +286,31 @@ router.getAsync('/rss/fetch/:campaignCid', passport.loggedIn, async (req, res) = return res.json(); }); +router.postAsync('/templates/:templateId/send', async (req, res) => { + const input = {}; + Object.keys(req.body).forEach(key => { + input[ + (key || '') + .toString() + .trim() + .toUpperCase() + ] = req.body[key] || ''; + }); + + try { + const templateSender = new TemplateSender({ + templateId: req.params.templateId + }); + const info = await templateSender.send({ + email: input.EMAIL, + subject: input.SUBJECT, + locale: req.locale, + variables: input.VARIABLES + }); + res.status(200).json({ data: info }); + } catch (e) { + throw new APIError(e.message, 400); + } +}); module.exports = router;