mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Added the last_seen_at
update on member page view
refs https://github.com/TryGhost/Team/issues/1306 - This adds a `MemberPageViewEvent` event when a page is viewed by a member (post/page/tag/author/...) - Integrates the `LastSeenAtUpdater` service that listens to the `MemberPageViewEvent` events to update `member.last_seen_at` - Follows the latest testing recommendation (end to end test + testing for side-effects)
This commit is contained in:
parent
73a049c942
commit
527ef79955
6 changed files with 98 additions and 9 deletions
|
@ -4,6 +4,8 @@ const express = require('../../shared/express');
|
|||
const cors = require('cors');
|
||||
const {URL} = require('url');
|
||||
const errors = require('@tryghost/errors');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {MemberPageViewEvent} = require('@tryghost/member-events');
|
||||
|
||||
// App requires
|
||||
const config = require('../../shared/config');
|
||||
|
@ -171,6 +173,14 @@ module.exports = function setupSiteApp(options = {}) {
|
|||
}
|
||||
});
|
||||
|
||||
siteApp.use(function (req, res, next) {
|
||||
if (req.member) {
|
||||
// This event needs memberLastSeenAt to avoid doing un-necessary database queries when updating `last_seen_at`
|
||||
DomainEvents.dispatch(MemberPageViewEvent.create({url: req.url, memberId: req.member.id, memberLastSeenAt: req.member.last_seen_at}, new Date()));
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
debug('General middleware done');
|
||||
|
||||
router = siteRoutes(options);
|
||||
|
|
|
@ -92,7 +92,7 @@ class GhostEventProcessor extends EventProcessor {
|
|||
await this.db.knex('members')
|
||||
.where('email', '=', event.recipientEmail)
|
||||
.andWhere(builder => builder
|
||||
.where('last_seen_at', '<', moment.utc(event.timestamp).tz(timezone).startOf('day').format('YYYY-MM-DD HH:mm:ss'))
|
||||
.where('last_seen_at', '<', moment.utc(event.timestamp).tz(timezone).startOf('day').utc().format('YYYY-MM-DD HH:mm:ss'))
|
||||
.orWhereNull('last_seen_at')
|
||||
)
|
||||
.update({
|
||||
|
|
|
@ -16,6 +16,8 @@ const models = require('../../models');
|
|||
const {GhostMailer} = require('../mail');
|
||||
const jobsService = require('../jobs');
|
||||
const VerificationTrigger = require('@tryghost/verification-trigger');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {LastSeenAtUpdater} = require('@tryghost/members-events-service');
|
||||
const events = require('../../lib/common/events');
|
||||
|
||||
const messages = {
|
||||
|
@ -139,7 +141,7 @@ module.exports = {
|
|||
sendVerificationEmail: ({subject, message, amountImported}) => {
|
||||
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
||||
const fromAddress = config.get('user_email');
|
||||
|
||||
|
||||
if (escalationAddress) {
|
||||
ghostMailer.send({
|
||||
subject,
|
||||
|
@ -158,6 +160,16 @@ module.exports = {
|
|||
eventRepository: membersApi.events
|
||||
});
|
||||
|
||||
new LastSeenAtUpdater({
|
||||
models: {
|
||||
Member: models.Member
|
||||
},
|
||||
services: {
|
||||
domainEvents: DomainEvents,
|
||||
settingsCache
|
||||
}
|
||||
});
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const collection = await models.SingleUseToken.fetchAll();
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
"@tryghost/custom-theme-settings-service": "0.3.1",
|
||||
"@tryghost/database-info": "0.1.0",
|
||||
"@tryghost/debug": "0.1.13",
|
||||
"@tryghost/domain-events": "0.1.8",
|
||||
"@tryghost/email-analytics-provider-mailgun": "1.0.7",
|
||||
"@tryghost/email-analytics-service": "1.0.5",
|
||||
"@tryghost/errors": "1.2.3",
|
||||
|
@ -81,8 +82,9 @@
|
|||
"@tryghost/limit-service": "1.0.9",
|
||||
"@tryghost/logging": "2.0.4",
|
||||
"@tryghost/magic-link": "1.0.19",
|
||||
"@tryghost/member-events": "0.4.0",
|
||||
"@tryghost/members-api": "5.0.4",
|
||||
"@tryghost/members-events-service": "0.1.2",
|
||||
"@tryghost/members-events-service": "0.3.1",
|
||||
"@tryghost/members-importer": "0.5.2",
|
||||
"@tryghost/members-offers": "0.10.7",
|
||||
"@tryghost/members-ssr": "1.0.22",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const assert = require('assert');
|
||||
const should = require('should');
|
||||
const sinon = require('sinon');
|
||||
const supertest = require('supertest');
|
||||
|
@ -5,6 +6,9 @@ const moment = require('moment');
|
|||
const testUtils = require('../utils');
|
||||
const configUtils = require('../utils/configUtils');
|
||||
const settingsCache = require('../../core/shared/settings-cache');
|
||||
const DomainEvents = require('@tryghost/domain-events');
|
||||
const {MemberPageViewEvent} = require('@tryghost/member-events');
|
||||
const models = require('../../core/server/models');
|
||||
|
||||
function assertContentIsPresent(res) {
|
||||
res.text.should.containEql('<h2 id="markdown">markdown</h2>');
|
||||
|
@ -233,6 +237,18 @@ describe('Front-end members behaviour', function () {
|
|||
.expect(200)
|
||||
.expect(assertContentIsAbsent);
|
||||
});
|
||||
|
||||
it('doesn\'t generate a MemberPageView event', async function () {
|
||||
const spy = sinon.spy();
|
||||
DomainEvents.subscribe(MemberPageViewEvent, spy);
|
||||
|
||||
await request
|
||||
.get('/free-to-see/')
|
||||
.expect(200)
|
||||
.expect(assertContentIsPresent);
|
||||
|
||||
assert(spy.notCalled, 'A page view from a non-member shouldn\'t generate a MemberPageViewEvent event');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as free member', function () {
|
||||
|
@ -277,8 +293,9 @@ describe('Front-end members behaviour', function () {
|
|||
});
|
||||
|
||||
describe('as free member with vip label', function () {
|
||||
const email = 'vip@test.com';
|
||||
before(async function () {
|
||||
await loginAsMember('vip@test.com');
|
||||
await loginAsMember(email);
|
||||
});
|
||||
|
||||
it('can read label-only post content', async function () {
|
||||
|
@ -287,15 +304,37 @@ describe('Front-end members behaviour', function () {
|
|||
.expect(200)
|
||||
.expect(assertContentIsPresent);
|
||||
});
|
||||
|
||||
it('generates a MemberPageView event', async function () {
|
||||
const spy = sinon.spy();
|
||||
DomainEvents.subscribe(MemberPageViewEvent, spy);
|
||||
|
||||
// Reset last_seen_at property
|
||||
let member = await models.Member.findOne({email});
|
||||
await models.Member.edit({last_seen_at: null}, {id: member.get('id')});
|
||||
|
||||
member = await models.Member.findOne({email});
|
||||
assert.equal(member.get('last_seen_at'), null, 'The member shouldn\'t have a `last_seen_at` property set before this test.');
|
||||
|
||||
await request
|
||||
.get('/free-to-see/')
|
||||
.expect(200)
|
||||
.expect(assertContentIsPresent);
|
||||
|
||||
assert(spy.calledOnce, 'A page view from a member should generate a MemberPageViewEvent event');
|
||||
member = await models.Member.findOne({email});
|
||||
assert.notEqual(member.get('last_seen_at'), null, 'The member should have a `last_seen_at` property after having visited a page while logged-in.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as paid member', function () {
|
||||
const email = 'paid@test.com';
|
||||
before(async function () {
|
||||
// membersService needs to be required after Ghost start so that settings
|
||||
// are pre-populated with defaults
|
||||
const membersService = require('../../core/server/services/members');
|
||||
|
||||
const signinLink = await membersService.api.getMagicLink('paid@test.com');
|
||||
const signinLink = await membersService.api.getMagicLink(email);
|
||||
const signinURL = new URL(signinLink);
|
||||
// request needs a relative path rather than full url with host
|
||||
const signinPath = `${signinURL.pathname}${signinURL.search}`;
|
||||
|
@ -344,6 +383,27 @@ describe('Front-end members behaviour', function () {
|
|||
.expect(200)
|
||||
.expect(assertContentIsPresent);
|
||||
});
|
||||
|
||||
it('generates a MemberPageView event', async function () {
|
||||
const spy = sinon.spy();
|
||||
DomainEvents.subscribe(MemberPageViewEvent, spy);
|
||||
|
||||
// Reset last_seen_at property
|
||||
let member = await models.Member.findOne({email});
|
||||
await models.Member.edit({last_seen_at: null}, {id: member.get('id')});
|
||||
|
||||
member = await models.Member.findOne({email});
|
||||
assert.equal(member.get('last_seen_at'), null, 'The member shouldn\'t have a `last_seen_at` property set before this test.');
|
||||
|
||||
await request
|
||||
.get('/free-to-see/')
|
||||
.expect(200)
|
||||
.expect(assertContentIsPresent);
|
||||
|
||||
assert(spy.calledOnce, 'A page view from a member should generate a MemberPageViewEvent event');
|
||||
member = await models.Member.findOne({email});
|
||||
assert.notEqual(member.get('last_seen_at'), null, 'The member should have a `last_seen_at` property after having visited a page while logged-in.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('as paid member with vip label', function () {
|
||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -1920,6 +1920,11 @@
|
|||
"@tryghost/root-utils" "^0.3.10"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@tryghost/domain-events@0.1.8":
|
||||
version "0.1.8"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/domain-events/-/domain-events-0.1.8.tgz#754af77b05336a689135971811ee438edda04876"
|
||||
integrity sha512-PalAdGOADidoxXg54F/QJEc1C9PwlQvWnIF1MksWY2Pp8XiH5mzeHisNnqXfXO3dEEopPPreIj7Lx/y6U9NAyQ==
|
||||
|
||||
"@tryghost/domain-events@^0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/domain-events/-/domain-events-0.1.7.tgz#dd6fa48886961c3e27889672a234bb516770d491"
|
||||
|
@ -2252,10 +2257,10 @@
|
|||
papaparse "5.3.1"
|
||||
pump "^3.0.0"
|
||||
|
||||
"@tryghost/members-events-service@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-events-service/-/members-events-service-0.1.2.tgz#e8bc9dac1eca98f0cbe0372bc2460573b24f21bf"
|
||||
integrity sha512-VbMAejI6daUiTyQCsA3FOhno7bPQNMWugbihAObwfyQayjoas8hYfrjk9/z9QMYh4N0A5JQugvXkL1gDQpbeLA==
|
||||
"@tryghost/members-events-service@0.3.1":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/members-events-service/-/members-events-service-0.3.1.tgz#209758435bd6aebe46426a440e68d76541ce095e"
|
||||
integrity sha512-bpjKYSzp1UjR6IPzzVLjoeSCbKT0rC7dr1SCwziavTD/2IuUA+ZKqxpPBeiGiQW+YFEvY6/NKXME+dr68fO6fw==
|
||||
dependencies:
|
||||
"@tryghost/domain-events" "0.1.8"
|
||||
"@tryghost/member-events" "0.4.0"
|
||||
|
|
Loading…
Add table
Reference in a new issue