mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-20 17:32:15 +03:00
f4fdb4fa6c
fixes https://github.com/TryGhost/Team/issues/2310 This moves the processing of the events from the event-processor to a new email-event-processor in the email-service package. - The `EmailEventProcessor` only translates events from providerId/emailId to their known emailId, memberId and recipientId, and dispatches the corresponding events. - Since `EmailEventProcessor` runs in a separate worker thread, we can't listen for the dispatched events on the main thread. To accomplish this communication, the events dispatched from the `EmailEventProcessor` class are 'posted' via the postMessage method and redispatched on the main thread. - A new `EmailEventStorage` class reacts to the email events and stores it in the database. This code mostly corresponds to the (now deleted) subclass of the old `EmailEventProcessor` - Updating a members last_seen_at timestamp has moved to the lastSeenAtUpdater. - Email events no longer store `ObjectID` because these are not encodable across threads via postMessage - Includes new E2E tests that test the storage of all supported Mailgun events. Note that in these tests we run the processing on the main thread instead of on a separate thread (couldn't do this because stubbing is not possible across threads) There are some missing pieces that will get added in later PRs (this PR focuses on porting the existing functionality): - Handling temporary failures/bounces - Capturing the error messages of bounce events
117 lines
5.4 KiB
JavaScript
117 lines
5.4 KiB
JavaScript
const {MemberPageViewEvent, MemberCommentEvent, MemberLinkClickEvent} = require('@tryghost/member-events');
|
|
const moment = require('moment-timezone');
|
|
const {IncorrectUsageError} = require('@tryghost/errors');
|
|
const {EmailOpenedEvent} = require('@tryghost/email-events');
|
|
|
|
/**
|
|
* Listen for `MemberViewEvent` to update the `member.last_seen_at` timestamp
|
|
*/
|
|
class LastSeenAtUpdater {
|
|
/**
|
|
* Initializes the event subscriber
|
|
* @param {Object} deps dependencies
|
|
* @param {Object} deps.services The list of service dependencies
|
|
* @param {any} deps.services.settingsCache The settings service
|
|
* @param {() => object} deps.getMembersApi - A function which returns an instance of members-api
|
|
*/
|
|
constructor({
|
|
services: {
|
|
settingsCache
|
|
},
|
|
getMembersApi
|
|
}) {
|
|
if (!getMembersApi) {
|
|
throw new IncorrectUsageError({message: 'Missing option getMembersApi'});
|
|
}
|
|
|
|
this._getMembersApi = getMembersApi;
|
|
this._settingsCacheService = settingsCache;
|
|
}
|
|
/**
|
|
* Subscribe to events of this domainEvents service
|
|
* @param {any} domainEvents The DomainEvents service
|
|
*/
|
|
subscribe(domainEvents) {
|
|
domainEvents.subscribe(MemberPageViewEvent, async (event) => {
|
|
await this.updateLastSeenAt(event.data.memberId, event.data.memberLastSeenAt, event.timestamp);
|
|
});
|
|
|
|
domainEvents.subscribe(MemberLinkClickEvent, async (event) => {
|
|
await this.updateLastSeenAt(event.data.memberId, event.data.memberLastSeenAt, event.timestamp);
|
|
});
|
|
|
|
domainEvents.subscribe(MemberCommentEvent, async (event) => {
|
|
await this.updateLastCommentedAt(event.data.memberId, event.timestamp);
|
|
});
|
|
|
|
domainEvents.subscribe(EmailOpenedEvent, async (event) => {
|
|
await this.updateLastSeenAtWithoutKnownLastSeen(event.memberId, event.timestamp);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Updates the member.last_seen_at field if it wasn't updated in the current day yet (in the publication timezone)
|
|
* Example: current time is 2022-02-28 18:00:00
|
|
* - memberLastSeenAt is 2022-02-27 23:00:00, timestamp is current time, then `last_seen_at` is set to the current time
|
|
* - memberLastSeenAt is 2022-02-28 01:00:00, timestamp is current time, then `last_seen_at` isn't changed
|
|
* @param {string} memberId The id of the member to be udpated
|
|
* @param {Date} timestamp The event timestamp
|
|
*/
|
|
async updateLastSeenAtWithoutKnownLastSeen(memberId, timestamp) {
|
|
// Fetch manually
|
|
const membersApi = this._getMembersApi();
|
|
const member = await membersApi.members.get({id: memberId}, {require: true});
|
|
const memberLastSeenAt = member.get('last_seen_at');
|
|
await this.updateLastSeenAt(memberId, memberLastSeenAt, timestamp);
|
|
}
|
|
|
|
/**
|
|
* Updates the member.last_seen_at field if it wasn't updated in the current day yet (in the publication timezone)
|
|
* Example: current time is 2022-02-28 18:00:00
|
|
* - memberLastSeenAt is 2022-02-27 23:00:00, timestamp is current time, then `last_seen_at` is set to the current time
|
|
* - memberLastSeenAt is 2022-02-28 01:00:00, timestamp is current time, then `last_seen_at` isn't changed
|
|
* @param {string} memberId The id of the member to be udpated
|
|
* @param {string|null} memberLastSeenAt The previous last_seen_at property value for the current member
|
|
* @param {Date} timestamp The event timestamp
|
|
*/
|
|
async updateLastSeenAt(memberId, memberLastSeenAt, timestamp) {
|
|
const timezone = this._settingsCacheService.get('timezone');
|
|
if (memberLastSeenAt === null || moment(moment.utc(timestamp).tz(timezone).startOf('day')).isAfter(memberLastSeenAt)) {
|
|
const membersApi = this._getMembersApi();
|
|
await membersApi.members.update({
|
|
last_seen_at: moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
|
}, {
|
|
id: memberId
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the member.last_seen_at field if it wasn't updated in the current day yet (in the publication timezone)
|
|
* Example: current time is 2022-02-28 18:00:00
|
|
* - memberLastSeenAt is 2022-02-27 23:00:00, timestamp is current time, then `last_seen_at` is set to the current time
|
|
* - memberLastSeenAt is 2022-02-28 01:00:00, timestamp is current time, then `last_seen_at` isn't changed
|
|
* @param {string} memberId The id of the member to be udpated
|
|
* @param {Date} timestamp The event timestamp
|
|
*/
|
|
async updateLastCommentedAt(memberId, timestamp) {
|
|
const membersApi = this._getMembersApi();
|
|
const member = await membersApi.members.get({id: memberId}, {require: true});
|
|
const timezone = this._settingsCacheService.get('timezone');
|
|
|
|
const memberLastSeenAt = member.get('last_seen_at');
|
|
const memberLastCommentedAt = member.get('last_commented_at');
|
|
|
|
if (memberLastSeenAt === null || moment(moment.utc(timestamp).tz(timezone).startOf('day')).isAfter(memberLastSeenAt) || memberLastCommentedAt === null || moment(moment.utc(timestamp).tz(timezone).startOf('day')).isAfter(memberLastCommentedAt)) {
|
|
await membersApi.members.update({
|
|
last_seen_at: moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss'),
|
|
last_commented_at: moment.utc(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
|
}, {
|
|
id: memberId
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = LastSeenAtUpdater;
|