diff --git a/core/server/index.js b/core/server/index.js
index 93429182f7..8762169b88 100644
--- a/core/server/index.js
+++ b/core/server/index.js
@@ -28,7 +28,7 @@ function initialiseServices() {
apps = require('./services/apps'),
xmlrpc = require('./services/xmlrpc'),
slack = require('./services/slack'),
- mega = require('./services/mega'),
+ {mega} = require('./services/mega'),
webhooks = require('./services/webhooks'),
scheduling = require('./adapters/scheduling');
diff --git a/core/server/services/mega.js b/core/server/services/mega.js
deleted file mode 100644
index 57ac121ed3..0000000000
--- a/core/server/services/mega.js
+++ /dev/null
@@ -1,61 +0,0 @@
-const common = require('../lib/common');
-const membersService = require('./members');
-const bulkEmailService = require('./bulk-email');
-const models = require('../models');
-
-const sendEmail = async (post) => {
- const emailTmpl = {
- subject: post.posts_meta.email_subject || post.title,
- html: post.html
- };
-
- const {members} = await membersService.api.members.list();
- const emails = members.map(m => m.email);
-
- return bulkEmailService.send(emailTmpl, emails);
-};
-
-function listener(model, options) {
- // CASE: do not send email if we import a database
- // TODO: refactor post.published events to never fire on importing
- if (options && options.importing) {
- return;
- }
-
- if (!model.get('send_email_when_published')) {
- return;
- }
-
- sendEmail(model.toJSON()).then(async () => {
- const deliveredEvents = await models.Action.findAll({
- filter: `event:delivered+resource_id:${model.id}`
- });
- if (deliveredEvents && deliveredEvents.toJSON().length > 0) {
- return;
- }
- let actor = {id: null, type: null};
- if (options.context && options.context.user) {
- actor = {
- id: options.context.user,
- type: 'user'
- };
- }
- const action = {
- event: 'delivered',
- resource_id: model.id,
- resource_type: 'post',
- actor_id: actor.id,
- actor_type: actor.type
- };
- return models.Action.add(action, {context: {internal: true}});
- });
-}
-
-function listen() {
- common.events.on('post.published', listener);
-}
-
-// Public API
-module.exports = {
- listen: listen
-};
diff --git a/core/server/services/mega/index.js b/core/server/services/mega/index.js
new file mode 100644
index 0000000000..e2c1f7140f
--- /dev/null
+++ b/core/server/services/mega/index.js
@@ -0,0 +1,3 @@
+module.exports = {
+ mega: require('./mega')
+};
diff --git a/core/server/services/mega/mega.js b/core/server/services/mega/mega.js
new file mode 100644
index 0000000000..d565cd56cc
--- /dev/null
+++ b/core/server/services/mega/mega.js
@@ -0,0 +1,92 @@
+const juice = require('juice');
+const common = require('../../lib/common');
+const api = require('../../api');
+const membersService = require('../members');
+const bulkEmailService = require('../bulk-email');
+const models = require('../../models');
+const template = require('./template');
+const settingsCache = require('../../services/settings/cache');
+const urlUtils = require('../../lib/url-utils');
+
+const getSite = () => {
+ return Object.assign({}, settingsCache.getPublic(), {
+ url: urlUtils.urlFor('home', true)
+ });
+};
+
+const sendEmail = async (post) => {
+ const emailTmpl = {
+ subject: post.email_subject || post.title,
+ html: juice(template({post, site: getSite()}))
+ };
+
+ const {members} = await membersService.api.members.list();
+ const emails = members.map(m => m.email);
+
+ return bulkEmailService.send(emailTmpl, emails);
+};
+
+// NOTE: serialization is needed to make sure we are using current API and do post transformations
+// such as image URL transformation from relative to absolute
+const serialize = async (model) => {
+ const frame = {options: {previous: true, context: {user: true}}};
+ const apiVersion = model.get('api_version') || 'v3';
+ const docName = 'posts';
+
+ await api.shared
+ .serializers
+ .handle
+ .output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame);
+
+ return frame.response[docName][0];
+};
+
+async function listener(model, options) {
+ // CASE: do not send email if we import a database
+ // TODO: refactor post.published events to never fire on importing
+ if (options && options.importing) {
+ return;
+ }
+
+ const post = await serialize(model);
+
+ if (!post.send_email_when_published) {
+ return;
+ }
+
+ const deliveredEvents = await models.Action.findAll({
+ filter: `event:delivered+resource_id:${model.id}`
+ });
+
+ if (deliveredEvents && deliveredEvents.toJSON().length > 0) {
+ return;
+ }
+
+ sendEmail(post).then(async () => {
+
+ let actor = {id: null, type: null};
+ if (options.context && options.context.user) {
+ actor = {
+ id: options.context.user,
+ type: 'user'
+ };
+ }
+ const action = {
+ event: 'delivered',
+ resource_id: model.id,
+ resource_type: 'post',
+ actor_id: actor.id,
+ actor_type: actor.type
+ };
+ return models.Action.add(action, {context: {internal: true}});
+ });
+}
+
+function listen() {
+ common.events.on('post.published', listener);
+}
+
+// Public API
+module.exports = {
+ listen: listen
+};
diff --git a/core/server/services/mega/template.js b/core/server/services/mega/template.js
new file mode 100644
index 0000000000..ab658c2810
--- /dev/null
+++ b/core/server/services/mega/template.js
@@ -0,0 +1,140 @@
+module.exports = ({post, site}) => `
+
+
+
+
+
+ ${post.title}!
+
+
+
+
+
+
+`;
diff --git a/package.json b/package.json
index f2b04055cd..3571e77f5a 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"js-yaml": "3.13.1",
"jsonpath": "1.0.2",
"jsonwebtoken": "8.5.1",
+ "juice": "5.2.0",
"keypair": "1.0.1",
"knex": "0.19.5",
"knex-migrator": "3.4.0",
diff --git a/yarn.lock b/yarn.lock
index 2a8be375bd..04ba3eb28e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -725,7 +725,7 @@ async@^2.0.0, async@^2.1.2, async@^2.6.0, async@^2.6.1, async@^2.6.3:
dependencies:
lodash "^4.17.14"
-async@^3.0.1:
+async@^3.0.1, async@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.1.0.tgz#42b3b12ae1b74927b5217d8c0016baaf62463772"
integrity sha512-4vx/aaY6j/j3Lw3fbCHNWP0pPaTCew3F6F3hYyl/tHs/ndmV1q7NW9T5yuJ2XAGwdQrP+6Wu20x06U4APo/iQQ==
@@ -1281,7 +1281,7 @@ cheerio-advanced-selectors@~2.0.1:
resolved "https://registry.yarnpkg.com/cheerio-advanced-selectors/-/cheerio-advanced-selectors-2.0.1.tgz#fb5ec70a4599e8cec1cf669c6d9b90a3fa969c48"
integrity sha512-5wHR8bpiD5pdUtaS81A6hnJezzoDzL1TLWfK6bxnLkIgEKPV26BlOdMCcvuj3fTE7JSalsTUeNU7AOD/u6bYhw==
-cheerio@0.22.0:
+cheerio@0.22.0, cheerio@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
integrity sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=
@@ -1539,6 +1539,11 @@ commander@2.20.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==
+commander@^2.15.1:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
commander@^2.19.0, commander@^2.20.0, commander@^2.9.0:
version "2.20.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.1.tgz#3863ce3ca92d0831dcf2a102f5fb4b5926afd0f9"
@@ -1946,6 +1951,14 @@ data-urls@^1.1.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
+datauri@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/datauri/-/datauri-2.0.0.tgz#ff0ee23729935a6bcc81f301621bed3e692bf3c7"
+ integrity sha512-zS2HSf9pI5XPlNZgIqJg/wCJpecgU/HA6E/uv2EfaWnW1EiTGLfy/EexTIsC9c99yoCOTXlqeeWk4FkCSuO3/g==
+ dependencies:
+ image-size "^0.7.3"
+ mimer "^1.0.0"
+
dateformat@~1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9"
@@ -4062,7 +4075,7 @@ image-size@0.8.3:
dependencies:
queue "6.0.1"
-image-size@^0.7.4:
+image-size@^0.7.3, image-size@^0.7.4:
version "0.7.5"
resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.7.5.tgz#269f357cf5797cb44683dfa99790e54c705ead04"
integrity sha512-Hiyv+mXHfFEP7LzUL/llg9RwFxxY+o9N3JVLIeG5E7iFIFAalxvRU9UZthBdYDEVnzHMgjnKJPPpay5BWf1g9g==
@@ -4737,6 +4750,19 @@ jsprim@^1.2.2:
json-schema "0.2.3"
verror "1.10.0"
+juice@5.2.0:
+ version "5.2.0"
+ resolved "https://registry.yarnpkg.com/juice/-/juice-5.2.0.tgz#a40ea144bde2845fe2aade46a81f493f8ea677a0"
+ integrity sha512-0l6GZmT3efexyaaay3SchKT5kG311N59TEFP5lfvEy0nz9SNqjx311plJ3b4jze7arsmDsiHQLh/xnAuk0HFTQ==
+ dependencies:
+ cheerio "^0.22.0"
+ commander "^2.15.1"
+ cross-spawn "^6.0.5"
+ deep-extend "^0.6.0"
+ mensch "^0.3.3"
+ slick "^1.12.2"
+ web-resource-inliner "^4.3.1"
+
just-extend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.0.2.tgz#f3f47f7dfca0f989c55410a7ebc8854b07108afc"
@@ -5127,7 +5153,7 @@ lodash.sortby@^4.7.0:
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
-lodash.unescape@^4.0.0:
+lodash.unescape@^4.0.0, lodash.unescape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
@@ -5362,6 +5388,11 @@ memoize-one@~5.1.1:
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
+mensch@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/mensch/-/mensch-0.3.3.tgz#e200ff4dd823717f8e0563b32e3f5481fca262b2"
+ integrity sha1-4gD/TdgjcX+OBWOzLj9UgfyiYrI=
+
meow@^3.3.0:
version "3.7.0"
resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -5547,6 +5578,11 @@ mimelib@~0.2.15:
addressparser "~0.3.2"
encoding "~0.1.7"
+mimer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/mimer/-/mimer-1.0.0.tgz#32251bef4dc7a63184db3a1082ed9be3abe0f3db"
+ integrity sha512-4ZJvCzfcwsBgPbkKXUzGoVZMWjv8IDIygkGzVc7uUYhgnK0t2LmGxxjdgH1i+pn0/KQfB5F/VKUJlfyTSOFQjg==
+
mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
@@ -7323,7 +7359,7 @@ request-promise@^4.2.4:
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
-"request@>=2.76.0 <3.0.0", request@^2.83.0, request@^2.87.0, request@^2.88.0:
+"request@>=2.76.0 <3.0.0", request@^2.78.0, request@^2.83.0, request@^2.87.0, request@^2.88.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==
@@ -7516,7 +7552,7 @@ safe-regex@^1.1.0:
dependencies:
ret "~0.1.10"
-"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
+"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@^2.1.2, safer-buffer@~2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
@@ -7784,6 +7820,11 @@ slice-ansi@^2.1.0:
astral-regex "^1.0.0"
is-fullwidth-code-point "^2.0.0"
+slick@^1.12.2:
+ version "1.12.2"
+ resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7"
+ integrity sha1-vQSN23TefRymkV+qSldXCzVQwtc=
+
smartquotes@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/smartquotes/-/smartquotes-2.3.1.tgz#01ebb595d6c7a9e24d90e8cb95c17d0e1af49407"
@@ -8768,6 +8809,11 @@ v8flags@^3.1.2, v8flags@^3.1.3, v8flags@~3.1.1:
dependencies:
homedir-polyfill "^1.0.1"
+valid-data-url@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/valid-data-url/-/valid-data-url-2.0.0.tgz#2220fa9f8d4e761ebd3f3bb02770f1212b810537"
+ integrity sha512-dyCZnv3aCey7yfTgIqdZanKl7xWAEEKCbgmR7SKqyK6QT/Z07ROactrgD1eA37C69ODRj7rNOjzKWVPh0EUjBA==
+
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
@@ -8855,6 +8901,21 @@ walkdir@^0.0.11:
resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532"
integrity sha1-oW0CXrkxvQO1LzCMrtD0D86+lTI=
+web-resource-inliner@^4.3.1:
+ version "4.3.3"
+ resolved "https://registry.yarnpkg.com/web-resource-inliner/-/web-resource-inliner-4.3.3.tgz#a5446b02bc11beb4cb5e764e928d9c1e4ef47f41"
+ integrity sha512-Qk19pohqZs3SoFUW4ZlOHlM8hsOnXhTpCrQ16b1qtJtKzJgO7NZLGP+/lcb2g3hWDQD39/LE/JYOn1Sjy7tn1A==
+ dependencies:
+ async "^3.1.0"
+ chalk "^2.4.2"
+ datauri "^2.0.0"
+ htmlparser2 "^3.9.2"
+ lodash.unescape "^4.0.1"
+ request "^2.78.0"
+ safer-buffer "^2.1.2"
+ valid-data-url "^2.0.0"
+ xtend "^4.0.2"
+
webidl-conversions@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
@@ -9035,7 +9096,7 @@ xss@~1.0.6:
commander "^2.9.0"
cssfilter "0.0.10"
-xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
+xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==