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}! + + + + + + + + + +
  +
+ + + + + + + + + + + +
+ + + ${post.html} + +
+
+ + + + + + +
+
 
+ + +`; 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==