Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into testing-scale
Browse files Browse the repository at this point in the history
* upstream/main:
  Added support for updating the site actor
  Ensured activities are delivered to all followers (#233)
  Fixed multi tenant handling of inboxes (#232)
  • Loading branch information
allouis committed Dec 18, 2024
2 parents 5c3e887 + 4c62b50 commit 4ff984f
Show file tree
Hide file tree
Showing 14 changed files with 605 additions and 151 deletions.
4 changes: 4 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ services:
- MQ_PUBSUB_HOST=pubsub:8085
- MQ_PUBSUB_TOPIC_NAME=activitypub_topic_changeme
- MQ_PUBSUB_SUBSCRIPTION_NAME=activitypub_subscription_changeme
- ACTIVITYPUB_COLLECTION_PAGE_SIZE=20
command: yarn build:watch
depends_on:
migrate:
Expand Down Expand Up @@ -115,6 +116,7 @@ services:
- MQ_PUBSUB_HOST=pubsub-testing:8085
- MQ_PUBSUB_TOPIC_NAME=activitypub_topic_changeme
- MQ_PUBSUB_SUBSCRIPTION_NAME=activitypub_subscription_changeme
- ACTIVITYPUB_COLLECTION_PAGE_SIZE=2
command: yarn build:watch
depends_on:
mysql-testing:
Expand Down Expand Up @@ -166,6 +168,8 @@ services:
- MYSQL_USER=ghost
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=activitypub
ports:
- "3308:3306"
healthcheck:
test: "mysql -ughost -ppassword activitypub -e 'select 1'"
interval: 1s
Expand Down
15 changes: 15 additions & 0 deletions features/followers.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Feature: Followers

Scenario: Activities are sent to all followers
Given we are followed by:
| name | type |
| Alice | Person |
| Bob | Person |
| Charlie | Person |
| Dave | Person |
And the list of followers is paginated across multiple pages
When we create a note "Note" with the content
"""
Hello, world!
"""
Then Activity "Note" is sent to all followers
124 changes: 124 additions & 0 deletions features/step_definitions/stepdefs.js
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,28 @@ BeforeAll(async () => {
},
},
);

ghostActivityPub.register(
{
method: 'GET',
endpoint: '/ghost/api/admin/site',
},
{
status: 200,
body: {
settings: {
site: {
title: 'Testing Blog',
icon: 'https://ghost.org/favicon.ico',
description: 'A blog for testing',
},
},
},
headers: {
'Content-Type': 'application/json',
},
},
);
});

AfterAll(async () => {
Expand Down Expand Up @@ -524,6 +546,51 @@ Given('we follow {string}', async function (name) {
}
});

Given('we are followed by:', async function (actors) {
for (const { name, type } of actors.hashes()) {
// Create the actor
this.actors[name] = await createActor(name, { type });

// Create the follow activity
const actor = this.actors[name];
const object = this.actors.Us;
const activity = await createActivity('Follow', object, actor);

const key = `Follow(Us)_${name}`;
this.activities[key] = activity;
this.objects[key] = object;

// Send the follow activity to the inbox
this.response = await fetchActivityPub(
'http://fake-ghost-activitypub/.ghost/activitypub/inbox/index',
{
method: 'POST',
body: JSON.stringify(activity),
},
);

await waitForInboxActivity(activity);
}
});

Given('the list of followers is paginated across multiple pages', async () => {
const followersResponse = await fetchActivityPub(
'http://fake-ghost-activitypub/.ghost/activitypub/followers/index',
);
const followersResponseJson = await followersResponse.json();

const followersFirstPageReponse = await fetchActivityPub(
followersResponseJson.first,
);
const followersFirstPageReponseJson =
await followersFirstPageReponse.json();

assert(
followersFirstPageReponseJson.next,
'Expected multiple pages of pagination but only got 1',
);
});

When('we like the object {string}', async function (name) {
const id = this.objects[name].id;
this.response = await fetchActivityPub(
Expand Down Expand Up @@ -868,6 +935,63 @@ Then(
},
);

Then(
'Activity {string} is sent to all followers',
async function (activityName) {
// Retrieve all followers
const followers = [];

const followersResponse = await fetchActivityPub(
'http://fake-ghost-activitypub/.ghost/activitypub/followers/index',
);
const followersResponseJson = await followersResponse.json();

const followersFirstPageResponse = await fetchActivityPub(
followersResponseJson.first,
);
const followersFirstPageResponseJson =
await followersFirstPageResponse.json();

followers.push(...followersFirstPageResponseJson.orderedItems);

let nextPage = followersFirstPageResponseJson.next;

while (nextPage) {
const nextPageResponse = await fetchActivityPub(nextPage);
const nextPageResponseJson = await nextPageResponse.json();

followers.push(...nextPageResponseJson.orderedItems);

nextPage = nextPageResponseJson.next;
}

// Check that the activity was sent to all followers
const activity = this.activities[activityName];

for (const follower of followers) {
const inbox = new URL(follower.inbox);

const found = await waitForRequest(
'POST',
inbox.pathname,
(call) => {
const json = JSON.parse(call.request.body);

return (
json.type === activity.type &&
json.object.id === activity.object.id
);
},
);

assert(
found,
`Activity "${activityName}" was not sent to "${follower.name}"`,
);
}
},
);

const webhooks = {
'post.published': {
post: {
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@cucumber/cucumber": "10.9.0",
"@fedify/cli": "1.3.0",
"@fedify/cli": "1.3.1",
"@types/jsonwebtoken": "9.0.7",
"@types/node": "20.17.6",
"@types/node-jose": "1.1.13",
Expand All @@ -43,7 +43,7 @@
"wiremock-captain": "3.5.0"
},
"dependencies": {
"@fedify/fedify": "1.3.0",
"@fedify/fedify": "1.3.1",
"@google-cloud/opentelemetry-cloud-trace-exporter": "2.4.1",
"@google-cloud/opentelemetry-cloud-trace-propagator": "0.20.0",
"@google-cloud/pubsub": "4.9.0",
Expand Down
43 changes: 19 additions & 24 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import {
} from './dispatchers';
import {
followAction,
getSiteDataHandler,
inboxHandler,
likeAction,
noteAction,
Expand Down Expand Up @@ -571,30 +572,29 @@ if (queue instanceof GCloudPubSubPushMessageQueue) {
app.post('/.ghost/activitypub/mq', spanWrapper(handlePushMessage(queue)));
}

app.use(async (ctx, next) => {
const request = ctx.req;
const host = request.header('host');
if (!host) {
ctx.get('logger').info('No Host header');
return new Response('No Host header', {
status: 401,
});
}

const scopedDb = scopeKvStore(db, ['sites', host]);

ctx.set('db', scopedDb);
ctx.set('globaldb', db);

await next();
});
// This needs to go before the middleware which loads the site
// Because the site doesn't always exist - this is how it's created
app.get(
'/.ghost/activitypub/site',
requireRole(GhostRole.Owner),
async (ctx) => {
const request = ctx.req;
const host = request.header('host');
if (!host) {
ctx.get('logger').info('No Host header');
return new Response('No Host header', {
status: 401,
});
}

const site = await getSite(host, true);

return new Response(JSON.stringify(site), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
},
getSiteDataHandler,
);

app.use(async (ctx, next) => {
Expand All @@ -606,9 +606,6 @@ app.use(async (ctx, next) => {
status: 401,
});
}

const scopedDb = scopeKvStore(db, ['sites', host]);

const site = await getSite(host);

if (!site) {
Expand All @@ -618,8 +615,6 @@ app.use(async (ctx, next) => {
});
}

ctx.set('db', scopedDb);
ctx.set('globaldb', db);
ctx.set('site', site);

await next();
Expand Down
5 changes: 0 additions & 5 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@ export const ACTOR_DEFAULT_HANDLE = 'index';
export const ACTOR_DEFAULT_NAME = 'Local Ghost site';
export const ACTOR_DEFAULT_ICON = 'https://ghost.org/favicon.ico';
export const ACTOR_DEFAULT_SUMMARY = 'This is a summary';

export const FOLLOWERS_PAGE_SIZE = 20;
export const FOLLOWING_PAGE_SIZE = 20;
export const LIKED_PAGE_SIZE = 20;
export const OUTBOX_PAGE_SIZE = 20;
Loading

0 comments on commit 4ff984f

Please sign in to comment.