mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
Updated activitypub app to utilise new activities endpoint (#21025)
refs [AP-377](https://linear.app/tryghost/issue/AP-377/inbox-returning-33mb-of-data), [TryGhost/ActivityPub#40](https://github.com/TryGhost/ActivityPub/pull/40) Updated activitypub app to utilise new activities endpoint which returns a paginated list of activities
This commit is contained in:
parent
8d957c3ec1
commit
d7ee3b2e42
4 changed files with 105 additions and 8 deletions
|
@ -443,4 +443,45 @@ describe('ActivityPubAPI', function () {
|
||||||
await api.follow('@user@domain.com');
|
await api.follow('@user@domain.com');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getAllActivities', function () {
|
||||||
|
test('It fetches all activities navigating pagination', async function () {
|
||||||
|
const fakeFetch = Fetch({
|
||||||
|
'https://auth.api/': {
|
||||||
|
response: JSONResponse({
|
||||||
|
identities: [{
|
||||||
|
token: 'fake-token'
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50': {
|
||||||
|
response: JSONResponse({
|
||||||
|
items: [{type: 'Create', object: {type: 'Note'}}],
|
||||||
|
nextCursor: 'next-cursor'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&cursor=next-cursor': {
|
||||||
|
response: JSONResponse({
|
||||||
|
items: [{type: 'Announce', object: {type: 'Article'}}],
|
||||||
|
nextCursor: null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const api = new ActivityPubAPI(
|
||||||
|
new URL('https://activitypub.api'),
|
||||||
|
new URL('https://auth.api'),
|
||||||
|
'index',
|
||||||
|
fakeFetch
|
||||||
|
);
|
||||||
|
|
||||||
|
const actual = await api.getAllActivities();
|
||||||
|
const expected: Activity[] = [
|
||||||
|
{type: 'Create', object: {type: 'Note'}},
|
||||||
|
{type: 'Announce', object: {type: 'Article'}}
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(actual).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -160,4 +160,52 @@ export class ActivityPubAPI {
|
||||||
const url = new URL(`.ghost/activitypub/actions/unlike/${encodeURIComponent(id)}`, this.apiUrl);
|
const url = new URL(`.ghost/activitypub/actions/unlike/${encodeURIComponent(id)}`, this.apiUrl);
|
||||||
await this.fetchJSON(url, 'POST');
|
await this.fetchJSON(url, 'POST');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get activitiesApiUrl() {
|
||||||
|
return new URL(`.ghost/activitypub/activities/${this.handle}`, this.apiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllActivities(): Promise<Activity[]> {
|
||||||
|
const LIMIT = 50;
|
||||||
|
|
||||||
|
const fetchActivities = async (url: URL): Promise<Activity[]> => {
|
||||||
|
const json = await this.fetchJSON(url);
|
||||||
|
|
||||||
|
// If the response is null, return early
|
||||||
|
if (json === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the response doesn't have an items array, return early
|
||||||
|
if (!('items' in json)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the response has an items property, but it's not an array
|
||||||
|
// use an empty array
|
||||||
|
const items = Array.isArray(json.items) ? json.items : [];
|
||||||
|
|
||||||
|
// If the response has a nextCursor property, fetch the next page
|
||||||
|
// recursively and concatenate the results
|
||||||
|
if ('nextCursor' in json && typeof json.nextCursor === 'string') {
|
||||||
|
const nextUrl = new URL(url);
|
||||||
|
|
||||||
|
nextUrl.searchParams.set('cursor', json.nextCursor);
|
||||||
|
nextUrl.searchParams.set('limit', LIMIT.toString());
|
||||||
|
|
||||||
|
const nextItems = await fetchActivities(nextUrl);
|
||||||
|
|
||||||
|
return items.concat(nextItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make a copy of the activities API URL and set the limit
|
||||||
|
const url = new URL(this.activitiesApiUrl);
|
||||||
|
url.searchParams.set('limit', LIMIT.toString());
|
||||||
|
|
||||||
|
// Fetch the activities
|
||||||
|
return fetchActivities(url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import React, {useState} from 'react';
|
||||||
import {type Activity} from './activities/ActivityItem';
|
import {type Activity} from './activities/ActivityItem';
|
||||||
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
import {ActorProperties, ObjectProperties} from '@tryghost/admin-x-framework/api/activitypub';
|
||||||
import {Button, Heading} from '@tryghost/admin-x-design-system';
|
import {Button, Heading} from '@tryghost/admin-x-design-system';
|
||||||
import {useBrowseInboxForUser} from '../MainContent';
|
import {useAllActivitiesForUser} from '../hooks/useActivityPubQueries';
|
||||||
|
|
||||||
interface InboxProps {}
|
interface InboxProps {}
|
||||||
|
|
||||||
|
@ -16,18 +16,15 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
||||||
const [, setArticleActor] = useState<ActorProperties | null>(null);
|
const [, setArticleActor] = useState<ActorProperties | null>(null);
|
||||||
const [layout, setLayout] = useState('inbox');
|
const [layout, setLayout] = useState('inbox');
|
||||||
|
|
||||||
// Retrieve activities from the inbox
|
// Retrieve all activities for the user
|
||||||
const {data: inboxActivities = []} = useBrowseInboxForUser('index');
|
let {data: activities = []} = useAllActivitiesForUser('index');
|
||||||
|
|
||||||
const activities = inboxActivities.filter((activity: Activity) => {
|
activities = activities.filter((activity: Activity) => {
|
||||||
const isCreate = activity.type === 'Create' && ['Article', 'Note'].includes(activity.object.type);
|
const isCreate = activity.type === 'Create' && ['Article', 'Note'].includes(activity.object.type);
|
||||||
const isAnnounce = activity.type === 'Announce' && activity.object.type === 'Note';
|
const isAnnounce = activity.type === 'Announce' && activity.object.type === 'Note';
|
||||||
|
|
||||||
return isCreate || isAnnounce;
|
return isCreate || isAnnounce;
|
||||||
})
|
});
|
||||||
// API endpoint currently returns items oldest-newest, so reverse them
|
|
||||||
// to show the most recent activities first
|
|
||||||
.reverse();
|
|
||||||
|
|
||||||
// Create a map of activity comments, grouping them by the parent activity
|
// Create a map of activity comments, grouping them by the parent activity
|
||||||
// This allows us to quickly look up all comments for a given activity
|
// This allows us to quickly look up all comments for a given activity
|
||||||
|
|
|
@ -165,3 +165,14 @@ export function useFollowersForUser(handle: string) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAllActivitiesForUser(handle: string) {
|
||||||
|
const siteUrl = useSiteUrl();
|
||||||
|
const api = createActivityPubAPI(handle, siteUrl);
|
||||||
|
return useQuery({
|
||||||
|
queryKey: [`activities:${handle}`],
|
||||||
|
async queryFn() {
|
||||||
|
return api.getAllActivities();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue