diff --git a/ghost/core/core/boot.js b/ghost/core/core/boot.js index 8a35b73c2a..477e59c858 100644 --- a/ghost/core/core/boot.js +++ b/ghost/core/core/boot.js @@ -315,6 +315,7 @@ async function initServices({config}) { const emailService = require('./server/services/email-service'); const emailAnalytics = require('./server/services/email-analytics'); const mentionsService = require('./server/services/mentions'); + const mentionsEmailReport = require('./server/services/mentions-email-report'); const tagsPublic = require('./server/services/tags-public'); const postsPublic = require('./server/services/posts-public'); const slackNotifications = require('./server/services/slack-notifications'); @@ -333,6 +334,7 @@ async function initServices({config}) { await Promise.all([ memberAttribution.init(), mentionsService.init(), + mentionsEmailReport.init(), staffService.init(), members.init(), tiers.init(), diff --git a/ghost/core/core/server/services/mentions-email-report/StartMentionEmailReportJob.js b/ghost/core/core/server/services/mentions-email-report/StartMentionEmailReportJob.js new file mode 100644 index 0000000000..a13e74ea14 --- /dev/null +++ b/ghost/core/core/server/services/mentions-email-report/StartMentionEmailReportJob.js @@ -0,0 +1,16 @@ +module.exports = class StartMentionEmailReportJob { + /** + * @param {Date} timestamp + */ + constructor(timestamp) { + this.data = null; + this.timestamp = timestamp; + } + + /** + * @param {Date} [timestamp] + */ + static create(data, timestamp) { + return new StartMentionEmailReportJob(timestamp ?? new Date); + } +}; diff --git a/ghost/core/core/server/services/mentions-email-report/index.js b/ghost/core/core/server/services/mentions-email-report/index.js new file mode 100644 index 0000000000..102ef66d4f --- /dev/null +++ b/ghost/core/core/server/services/mentions-email-report/index.js @@ -0,0 +1 @@ +module.exports = require('./service'); diff --git a/ghost/core/core/server/services/mentions-email-report/job.js b/ghost/core/core/server/services/mentions-email-report/job.js new file mode 100644 index 0000000000..1e66edb7c8 --- /dev/null +++ b/ghost/core/core/server/services/mentions-email-report/job.js @@ -0,0 +1,11 @@ +const {parentPort} = require('worker_threads'); +const StartMentionEmailReportJob = require('./StartMentionEmailReportJob'); + +if (parentPort) { + parentPort.postMessage({ + event: { + type: StartMentionEmailReportJob.name + } + }); + parentPort.postMessage('done'); +} diff --git a/ghost/core/core/server/services/mentions-email-report/service.js b/ghost/core/core/server/services/mentions-email-report/service.js new file mode 100644 index 0000000000..ed0b4c11f5 --- /dev/null +++ b/ghost/core/core/server/services/mentions-email-report/service.js @@ -0,0 +1,142 @@ +const MentionEmailReportJob = require('@tryghost/mentions-email-report'); + +/** + * @typedef {import('@tryghost/mentions-email-report/lib/mentions-email-report').MentionReport} MentionReport + * @typedef {import('@tryghost/mentions-email-report/lib/mentions-email-report').MentionReportRecipient} MentionReportRecipient + */ + +let initialised = false; + +module.exports = { + async init() { + if (initialised) { + return; + } + + const mentions = require('../mentions'); + const mentionReportGenerator = { + getMentionReport(startDate, endDate) { + return mentions.api.getMentionReport(startDate, endDate); + } + }; + + const models = require('../../models'); + const mentionReportRecipientRepository = { + async getMentionReportRecipients() { + const users = await models.User.getEmailAlertUsers('mention-received'); + return users.map((model) => { + return { + email: model.email, + slug: model.slug + }; + }); + } + }; + + const staffService = require('../staff'); + const mentionReportEmailView = { + /** + * @returns {Promise} + */ + async renderSubject() { + return 'Mention Report'; + }, + + /** + * @param {MentionReport} report + * @param {MentionReportRecipient} recipient + * @returns {Promise} + */ + async renderHTML(report, recipient) { + return staffService.api.emails.renderHTML('mention-report', { + report: report, + recipient: recipient, + hasMoreMentions: report.mentions.length > 5 + }); + }, + + /** + * @param {MentionReport} report + * @param {MentionReportRecipient} recipient + * @returns {Promise} + */ + async renderText(report, recipient) { + return staffService.api.emails.renderText('mention-report', { + report: report, + recipient: recipient + }); + } + }; + + const settingsCache = require('../../../shared/settings-cache'); + const mentionReportHistoryService = { + async getLatestReportDate() { + const setting = settingsCache.get('lastMentionsReportEmailTimestamp'); + const parsedDate = Date.parse(setting); + + // Protect against missing/bad data + if (Number.isNaN(parsedDate)) { + const date = new Date(); + date.setDate(date.getDate() - 1); + return date; + } + + return new Date(parsedDate); + }, + async setLatestReportDate(date) { + await models.Settings.edit({ + key: 'lastMentionsReportEmailTimestamp', + value: date + }); + } + }; + + const mail = require('../mail'); + const mailer = new mail.GhostMailer(); + const emailService = { + async send(to, subject, html, text) { + return mailer.send({ + to, + subject, + html, + text + }); + } + }; + + const job = new MentionEmailReportJob({ + mentionReportGenerator, + mentionReportRecipientRepository, + mentionReportEmailView, + mentionReportHistoryService, + emailService + }); + + const mentionsJobs = require('../mentions-jobs'); + + const DomainEvents = require('@tryghost/domain-events'); + const StartMentionEmailReportJob = require('./StartMentionEmailReportJob'); + + const labs = require('../../../shared/labs'); + DomainEvents.subscribe(StartMentionEmailReportJob, () => { + if (labs.isSet('webmentionEmails')) { + job.sendLatestReport(); + } + }); + + // Kick off the job on boot, this will make sure that we send a missing report if needed + DomainEvents.dispatch(StartMentionEmailReportJob.create()); + + const s = Math.floor(Math.random() * 60); // 0-59 + const m = Math.floor(Math.random() * 60); // 0-59 + + // Schedules a job every hour at a random minute and second to send the latest report + mentionsJobs.addJob({ + name: 'mentions-email-report', + job: require('path').resolve(__dirname, './job.js'), + at: `${s} ${m} * * * *` + }); + + initialised = true; + } +}; diff --git a/ghost/core/package.json b/ghost/core/package.json index 046f0279a8..12d4375353 100644 --- a/ghost/core/package.json +++ b/ghost/core/package.json @@ -114,6 +114,7 @@ "@tryghost/members-offers": "0.0.0", "@tryghost/members-ssr": "0.0.0", "@tryghost/members-stripe-service": "0.0.0", + "@tryghost/mentions-email-report": "0.0.0", "@tryghost/metrics": "1.0.22", "@tryghost/milestones": "0.0.0", "@tryghost/minifier": "0.0.0",