Skip to content

Commit 625bcc8

Browse files
authored
Updated the global feed to render content from top publishers (#1405)
ref https://linear.app/ghost/issue/BER-2935 - this is the second iteration of the global feed experiment and the code is not final - used integration tests to test the new behaviour and removed cucumber tests for now, as we're going to either drop the experiment or re-write the logic if we expand further on the experiment
1 parent 3d080cf commit 625bcc8

File tree

8 files changed

+356
-164
lines changed

8 files changed

+356
-164
lines changed

features/global-feed.feature

Lines changed: 0 additions & 14 deletions
This file was deleted.

features/step_definitions/global_feed_steps.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

features/step_definitions/index.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,7 @@ import { resolve } from 'node:path';
1010

1111
import jose from 'node-jose';
1212

13-
import {
14-
createGlobalFeedUser,
15-
getClient,
16-
reset as resetDatabase,
17-
} from '../support/db.js';
13+
import { getClient, reset as resetDatabase } from '../support/db.js';
1814
import { getWebhookSecret } from '../support/fixtures.js';
1915
import { getCurrentDirectory } from '../support/path.js';
2016
import { fetchActivityPub } from '../support/request.js';
@@ -154,7 +150,6 @@ async function setupSelfSite() {
154150
Before(async function reset() {
155151
await resetWiremock();
156152
await resetDatabase();
157-
await createGlobalFeedUser();
158153
await await Promise.all([
159154
setupSelfSite(),
160155
fetchActivityPub('https://alice.test/.ghost/activitypub/v1/site'),

features/support/db.js

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -56,48 +56,3 @@ export async function reset() {
5656
}
5757
});
5858
}
59-
60-
export async function createGlobalFeedUser() {
61-
const db = getClient();
62-
63-
await db.transaction(async (trx) => {
64-
const [siteId] = await trx('sites').insert({
65-
host: 'ap-global-feed.ghost.io',
66-
webhook_secret: trx.raw(
67-
"LOWER(SHA2('ap-global-feed.ghost.io', 256))",
68-
),
69-
ghost_pro: 1,
70-
});
71-
72-
const [accountId] = await trx('accounts').insert({
73-
username: 'index',
74-
name: 'ActivityPub Global Feed',
75-
bio: 'ActivityPub Global Feed',
76-
avatar_url: null,
77-
banner_image_url: null,
78-
url: 'https://ap-global-feed.ghost.io/',
79-
custom_fields: null,
80-
ap_id: 'https://ap-global-feed.ghost.io/.ghost/activitypub/users/index',
81-
ap_inbox_url:
82-
'https://ap-global-feed.ghost.io/.ghost/activitypub/inbox/index',
83-
ap_shared_inbox_url: null,
84-
ap_public_key: null,
85-
ap_private_key: null,
86-
ap_outbox_url:
87-
'https://ap-global-feed.ghost.io/.ghost/activitypub/outbox/index',
88-
ap_following_url:
89-
'https://ap-global-feed.ghost.io/.ghost/activitypub/following/index',
90-
ap_followers_url:
91-
'https://ap-global-feed.ghost.io/.ghost/activitypub/followers/index',
92-
ap_liked_url:
93-
'https://ap-global-feed.ghost.io/.ghost/activitypub/liked/index',
94-
uuid: trx.raw('UUID()'),
95-
domain: 'ap-global-feed.ghost.io',
96-
});
97-
98-
await trx('users').insert({
99-
account_id: accountId,
100-
site_id: siteId,
101-
});
102-
});
103-
}

features/support/global-feed.js

Lines changed: 0 additions & 50 deletions
This file was deleted.

src/feed/feed.service.integration.test.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { KnexPostRepository } from '@/post/post.repository.knex';
2323
import { SiteService } from '@/site/site.service';
2424
import { generateTestCryptoKeyPair } from '@/test/crypto-key-pair';
2525
import { createTestDb } from '@/test/db';
26+
import { TOP_PUBLISHERS } from './top-publishers';
2627

2728
describe('FeedService', () => {
2829
let events: AsyncEvents;
@@ -899,56 +900,97 @@ describe('FeedService', () => {
899900
expect(feedEntry.published_at).toEqual(originalPublishDate);
900901
});
901902

902-
it('should add Article posts to the global feed user', async () => {
903+
it('should add articles from top publishers to the global feed', async () => {
903904
const feedService = new FeedService(client, moderationService);
904905

905906
// Create the global feed account
906907
const globalAccount = await createInternalAccount(
907908
'ap-global-feed.ghost.io',
908909
);
909910

910-
// Create an author and an Article post
911-
const authorAccount = await createInternalAccount('author.com');
912-
const articlePost = await createPost(authorAccount, {
911+
// Create a top publisher
912+
const topPublisherAccount = await createInternalAccount(
913+
'top-publisher-author-articles.com',
914+
);
915+
TOP_PUBLISHERS.add(topPublisherAccount.id);
916+
917+
// Create article
918+
const articlePost = await createPost(topPublisherAccount, {
913919
type: PostType.Article,
914920
audience: Audience.Public,
915921
});
916922
await postRepository.save(articlePost);
917923

918-
// Add the post to feeds
924+
// Add the article to feeds
919925
await feedService.addPostToFeeds(articlePost as PublicPost);
920926

921-
// Verify the post was added to the global feed
927+
// Verify the article was added to the global feed
922928
const globalFeed = await getFeedDataForAccount(globalAccount);
923929
expect(globalFeed).toHaveLength(1);
924930
expect(globalFeed[0]).toMatchObject({
925931
post_id: articlePost.id,
926-
author_id: authorAccount.id,
932+
author_id: topPublisherAccount.id,
927933
});
934+
935+
// Cleanup
936+
TOP_PUBLISHERS.delete(topPublisherAccount.id);
928937
});
929938

930-
it('should NOT add Note posts to the global feed user', async () => {
939+
it('should NOT add articles from other publishers to the global feed', async () => {
931940
const feedService = new FeedService(client, moderationService);
932941

933942
// Create the global feed account
934943
const globalAccount = await createInternalAccount(
935944
'ap-global-feed.ghost.io',
936945
);
937946

938-
// Create an author and a Note post
947+
// Create author
939948
const authorAccount = await createInternalAccount('author.com');
940-
const notePost = await createPost(authorAccount, {
949+
950+
// Create article
951+
const articlePost = await createPost(authorAccount, {
952+
type: PostType.Article,
953+
audience: Audience.Public,
954+
});
955+
await postRepository.save(articlePost);
956+
957+
// Add the article to feeds
958+
await feedService.addPostToFeeds(articlePost as PublicPost);
959+
960+
// Verify the article was NOT added to the global feed
961+
const globalFeed = await getFeedDataForAccount(globalAccount);
962+
expect(globalFeed).toHaveLength(0);
963+
});
964+
965+
it('should NOT add notes to the global feed', async () => {
966+
const feedService = new FeedService(client, moderationService);
967+
968+
// Create the global feed account
969+
const globalAccount = await createInternalAccount(
970+
'ap-global-feed.ghost.io',
971+
);
972+
973+
// Create a top publisher
974+
const topPublisherAccount = await createInternalAccount(
975+
'top-publisher-author-notes.com',
976+
);
977+
TOP_PUBLISHERS.add(topPublisherAccount.id);
978+
979+
const notePost = await createPost(topPublisherAccount, {
941980
type: PostType.Note,
942981
audience: Audience.Public,
943982
});
944983
await postRepository.save(notePost);
945984

946-
// Add the post to feeds
985+
// Add the note to feeds
947986
await feedService.addPostToFeeds(notePost as PublicPost);
948987

949-
// Verify the post was NOT added to the global feed
988+
// Verify the note was NOT added to the global feed
950989
const globalFeed = await getFeedDataForAccount(globalAccount);
951990
expect(globalFeed).toHaveLength(0);
991+
992+
// Cleanup
993+
TOP_PUBLISHERS.delete(topPublisherAccount.id);
952994
});
953995
});
954996

src/feed/feed.service.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
PostType,
1111
type PublicPost,
1212
} from '@/post/post.entity';
13-
13+
import { isTopPublisher } from './top-publishers';
1414
export type FeedType = 'Inbox' | 'Feed';
1515

1616
export interface GetFeedDataOptions {
@@ -408,18 +408,20 @@ export class FeedService {
408408
targetUserIds.add(follower.user_id);
409409
}
410410

411-
// Add the post to the feeds
411+
// Add post to the users feeds
412412
const userIds = await this.moderationService.filterUsersForPost(
413413
Array.from(targetUserIds).map(Number),
414414
post,
415415
repostedBy ?? undefined,
416416
);
417417

418-
// Ensure the post is added to the global feed if the author is a Ghost publisher -
419-
// We are assuming that if the post is an Article, the author is a Ghost publisher
420-
// https://linear.app/ghost/project/activitypub-global-feed-discovery-ed06ad7be6b7
418+
// Add post to the global discovery feed if it's long-form and authored by a top publisher
421419
const globalFeedUserId = await this.getGlobalFeedUserId();
422-
if (globalFeedUserId !== null && post.type === PostType.Article) {
420+
if (
421+
globalFeedUserId !== null &&
422+
post.type === PostType.Article &&
423+
isTopPublisher(post.author.id)
424+
) {
423425
userIds.push(globalFeedUserId);
424426
}
425427

0 commit comments

Comments
 (0)