From 1290477d7168360c8ee019287c4677efe149ec1c Mon Sep 17 00:00:00 2001 From: Simon Backx Date: Fri, 23 Sep 2022 10:34:33 +0200 Subject: [PATCH] Added member last seen update on link click (#15459) fixes https://github.com/TryGhost/Team/issues/1952 Adds a new MemberLinkClickEvent event that is fired when a member clicks a link. This code has been added to the `linkClickRepository` because that is the only place that has access to the member model (and the event requires the id and current last seen at value). The LastSeenAtUpdater listens for this event and updates the timestamp if required. --- .../LinkClickRepository.js | 21 +++++++++++--- .../services/link-click-tracking/index.js | 9 ++++-- ghost/member-events/index.js | 3 +- .../member-events/lib/MemberLinkClickEvent.js | 28 +++++++++++++++++++ .../lib/last-seen-at-updater.js | 6 +++- 5 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 ghost/member-events/lib/MemberLinkClickEvent.js diff --git a/ghost/core/core/server/services/link-click-tracking/LinkClickRepository.js b/ghost/core/core/server/services/link-click-tracking/LinkClickRepository.js index cd9e9022b9..fad11eed7a 100644 --- a/ghost/core/core/server/services/link-click-tracking/LinkClickRepository.js +++ b/ghost/core/core/server/services/link-click-tracking/LinkClickRepository.js @@ -2,24 +2,34 @@ const {LinkClick} = require('@tryghost/link-tracking'); const ObjectID = require('bson-objectid').default; module.exports = class LinkClickRepository { + /** @type {Object} */ + #MemberLinkClickEventModel; + /** @type {Object} */ #MemberLinkClickEvent; /** @type {object} */ #Member; + /** @type {object} */ + #DomainEvents; + /** * @param {object} deps - * @param {object} deps.MemberLinkClickEvent Bookshelf Model + * @param {object} deps.MemberLinkClickEventModel Bookshelf Model * @param {object} deps.Member Bookshelf Model + * @param {object} deps.MemberLinkClickEvent Event + * @param {object} deps.DomainEvents */ constructor(deps) { - this.#MemberLinkClickEvent = deps.MemberLinkClickEvent; + this.#MemberLinkClickEventModel = deps.MemberLinkClickEventModel; this.#Member = deps.Member; + this.#MemberLinkClickEvent = deps.MemberLinkClickEvent; + this.#DomainEvents = deps.DomainEvents; } async getAll(options) { - const collection = await this.#MemberLinkClickEvent.findAll(options); + const collection = await this.#MemberLinkClickEventModel.findAll(options); const result = []; @@ -45,12 +55,15 @@ module.exports = class LinkClickRepository { return; } - const model = await this.#MemberLinkClickEvent.add({ + const model = await this.#MemberLinkClickEventModel.add({ // Only store the parthname (no support for variable query strings) link_id: linkClick.link_id.toHexString(), member_id: member.id }, {}); linkClick.event_id = ObjectID.createFromHexString(model.id); + + // Dispatch event + this.#DomainEvents.dispatch(this.#MemberLinkClickEvent.create({memberId: member.id, memberLastSeenAt: member.get('last_seen_at'), linkId: linkClick.link_id.toHexString()}, new Date())); } }; diff --git a/ghost/core/core/server/services/link-click-tracking/index.js b/ghost/core/core/server/services/link-click-tracking/index.js index 9a1ff4f04c..4c8a4d7a77 100644 --- a/ghost/core/core/server/services/link-click-tracking/index.js +++ b/ghost/core/core/server/services/link-click-tracking/index.js @@ -16,6 +16,9 @@ class LinkTrackingServiceWrapper { // Wire up all the dependencies const models = require('../../models'); + const {MemberLinkClickEvent} = require('@tryghost/member-events'); + const DomainEvents = require('@tryghost/domain-events'); + const {LinkTrackingService} = require('@tryghost/link-tracking'); const postLinkRepository = new PostLinkRepository({ @@ -24,8 +27,10 @@ class LinkTrackingServiceWrapper { }); const linkClickRepository = new LinkClickRepository({ - MemberLinkClickEvent: models.MemberLinkClickEvent, - Member: models.Member + MemberLinkClickEventModel: models.MemberLinkClickEvent, + Member: models.Member, + MemberLinkClickEvent: MemberLinkClickEvent, + DomainEvents }); // Expose the service diff --git a/ghost/member-events/index.js b/ghost/member-events/index.js index e99c4e1c42..2cdf54bd3b 100644 --- a/ghost/member-events/index.js +++ b/ghost/member-events/index.js @@ -9,5 +9,6 @@ module.exports = { MemberPageViewEvent: require('./lib/MemberPageViewEvent'), SubscriptionCreatedEvent: require('./lib/SubscriptionCreatedEvent'), MemberCommentEvent: require('./lib/MemberCommentEvent'), - SubscriptionCancelledEvent: require('./lib/SubscriptionCancelledEvent') + SubscriptionCancelledEvent: require('./lib/SubscriptionCancelledEvent'), + MemberLinkClickEvent: require('./lib/MemberLinkClickEvent') }; diff --git a/ghost/member-events/lib/MemberLinkClickEvent.js b/ghost/member-events/lib/MemberLinkClickEvent.js new file mode 100644 index 0000000000..643cccf691 --- /dev/null +++ b/ghost/member-events/lib/MemberLinkClickEvent.js @@ -0,0 +1,28 @@ +/** + * @typedef {object} MemberLinkClickEventData + * @prop {string} memberId + * @prop {string} memberLastSeenAt + * @prop {string} linkId + */ + +/** + * Server-side event firing on page views (page, post, tags...) + */ +module.exports = class MemberLinkClickEvent { + /** + * @param {MemberLinkClickEventData} data + * @param {Date} timestamp + */ + constructor(data, timestamp) { + this.data = data; + this.timestamp = timestamp; + } + + /** + * @param {MemberLinkClickEventData} data + * @param {Date} [timestamp] + */ + static create(data, timestamp) { + return new MemberLinkClickEvent(data, timestamp || new Date); + } +}; diff --git a/ghost/members-events-service/lib/last-seen-at-updater.js b/ghost/members-events-service/lib/last-seen-at-updater.js index 4bbec23502..03094e4dfd 100644 --- a/ghost/members-events-service/lib/last-seen-at-updater.js +++ b/ghost/members-events-service/lib/last-seen-at-updater.js @@ -1,4 +1,4 @@ -const {MemberPageViewEvent, MemberCommentEvent} = require('@tryghost/member-events'); +const {MemberPageViewEvent, MemberCommentEvent, MemberLinkClickEvent} = require('@tryghost/member-events'); const moment = require('moment-timezone'); const {IncorrectUsageError} = require('@tryghost/errors'); @@ -35,6 +35,10 @@ class LastSeenAtUpdater { 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); });