mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-04 02:01:58 -05:00
Updated activities tab to use the activities endpoint in the activitypub app (#21037)
refs [AP-377](https://linear.app/tryghost/issue/AP-377/inbox-returning-33mb-of-data), [TryGhost/ActivityPub#43](https://github.com/TryGhost/ActivityPub/pull/43) Updated activities tab to use the activities endpoint in the activitypub app
This commit is contained in:
parent
f9bb2d0ba7
commit
a886d22437
6 changed files with 50 additions and 65 deletions
|
@ -24,23 +24,6 @@ export function useBrowseInboxForUser(handle: string) {
|
|||
});
|
||||
}
|
||||
|
||||
export function useBrowseOutboxForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
const siteUrl = siteData?.url ?? window.location.origin;
|
||||
const api = new ActivityPubAPI(
|
||||
new URL(siteUrl),
|
||||
new URL('/ghost/api/admin/identities/', window.location.origin),
|
||||
handle
|
||||
);
|
||||
return useQuery({
|
||||
queryKey: [`outbox:${handle}`],
|
||||
async queryFn() {
|
||||
return api.getOutbox();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useFollowersForUser(handle: string) {
|
||||
const site = useBrowseSite();
|
||||
const siteData = site.data?.site;
|
||||
|
|
|
@ -454,13 +454,13 @@ describe('ActivityPubAPI', function () {
|
|||
}]
|
||||
})
|
||||
},
|
||||
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50': {
|
||||
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&includeOwn=false': {
|
||||
response: JSONResponse({
|
||||
items: [{type: 'Create', object: {type: 'Note'}}],
|
||||
nextCursor: 'next-cursor'
|
||||
})
|
||||
},
|
||||
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&cursor=next-cursor': {
|
||||
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&includeOwn=false&cursor=next-cursor': {
|
||||
response: JSONResponse({
|
||||
items: [{type: 'Announce', object: {type: 'Article'}}],
|
||||
nextCursor: null
|
||||
|
@ -483,5 +483,37 @@ describe('ActivityPubAPI', function () {
|
|||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
test('It fetches a user\'s own activities', async function () {
|
||||
const fakeFetch = Fetch({
|
||||
'https://auth.api/': {
|
||||
response: JSONResponse({
|
||||
identities: [{
|
||||
token: 'fake-token'
|
||||
}]
|
||||
})
|
||||
},
|
||||
'https://activitypub.api/.ghost/activitypub/activities/index?limit=50&includeOwn=true': {
|
||||
response: JSONResponse({
|
||||
items: [{type: 'Create', object: {type: 'Note'}}],
|
||||
nextCursor: null
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
const api = new ActivityPubAPI(
|
||||
new URL('https://activitypub.api'),
|
||||
new URL('https://auth.api'),
|
||||
'index',
|
||||
fakeFetch
|
||||
);
|
||||
|
||||
const actual = await api.getAllActivities(true);
|
||||
const expected: Activity[] = [
|
||||
{type: 'Create', object: {type: 'Note'}}
|
||||
];
|
||||
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -53,24 +53,6 @@ export class ActivityPubAPI {
|
|||
return [];
|
||||
}
|
||||
|
||||
get outboxApiUrl() {
|
||||
return new URL(`.ghost/activitypub/outbox/${this.handle}`, this.apiUrl);
|
||||
}
|
||||
|
||||
async getOutbox(): Promise<Activity[]> {
|
||||
const json = await this.fetchJSON(this.outboxApiUrl);
|
||||
if (json === null) {
|
||||
return [];
|
||||
}
|
||||
if ('orderedItems' in json) {
|
||||
return Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
|
||||
}
|
||||
if ('items' in json) {
|
||||
return Array.isArray(json.items) ? json.items : [json.items];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
get followingApiUrl() {
|
||||
return new URL(`.ghost/activitypub/following/${this.handle}`, this.apiUrl);
|
||||
}
|
||||
|
@ -165,7 +147,7 @@ export class ActivityPubAPI {
|
|||
return new URL(`.ghost/activitypub/activities/${this.handle}`, this.apiUrl);
|
||||
}
|
||||
|
||||
async getAllActivities(): Promise<Activity[]> {
|
||||
async getAllActivities(includeOwn: boolean = false): Promise<Activity[]> {
|
||||
const LIMIT = 50;
|
||||
|
||||
const fetchActivities = async (url: URL): Promise<Activity[]> => {
|
||||
|
@ -192,6 +174,7 @@ export class ActivityPubAPI {
|
|||
|
||||
nextUrl.searchParams.set('cursor', json.nextCursor);
|
||||
nextUrl.searchParams.set('limit', LIMIT.toString());
|
||||
nextUrl.searchParams.set('includeOwn', includeOwn.toString());
|
||||
|
||||
const nextItems = await fetchActivities(nextUrl);
|
||||
|
||||
|
@ -204,6 +187,7 @@ export class ActivityPubAPI {
|
|||
// Make a copy of the activities API URL and set the limit
|
||||
const url = new URL(this.activitiesApiUrl);
|
||||
url.searchParams.set('limit', LIMIT.toString());
|
||||
url.searchParams.set('includeOwn', includeOwn.toString());
|
||||
|
||||
// Fetch the activities
|
||||
return fetchActivities(url);
|
||||
|
|
|
@ -9,8 +9,8 @@ import ArticleModal from './feed/ArticleModal';
|
|||
import MainNavigation from './navigation/MainNavigation';
|
||||
|
||||
import getUsername from '../utils/get-username';
|
||||
import {useBrowseInboxForUser, useBrowseOutboxForUser, useFollowersForUser} from '../MainContent';
|
||||
import {useSiteUrl} from '../hooks/useActivityPubQueries';
|
||||
import {useAllActivitiesForUser, useSiteUrl} from '../hooks/useActivityPubQueries';
|
||||
import {useFollowersForUser} from '../MainContent';
|
||||
|
||||
interface ActivitiesProps {}
|
||||
|
||||
|
@ -88,13 +88,7 @@ const getActivityBadge = (activity: Activity): AvatarBadge => {
|
|||
const Activities: React.FC<ActivitiesProps> = ({}) => {
|
||||
const user = 'index';
|
||||
|
||||
// Retrieve activities from the inbox AND the outbox
|
||||
// Why the need for the outbox? The outbox contains activities that the user
|
||||
// has performed, and we sometimes need information about the object
|
||||
// associated with the activity (i.e when displaying the name of an article
|
||||
// that a reply was made to)
|
||||
const {data: inboxActivities = []} = useBrowseInboxForUser(user);
|
||||
const {data: outboxActivities = []} = useBrowseOutboxForUser(user);
|
||||
let {data: activities = []} = useAllActivitiesForUser({handle: 'index', includeOwn: true});
|
||||
const siteUrl = useSiteUrl();
|
||||
|
||||
// Create a map of activity objects from activities in the inbox and outbox.
|
||||
|
@ -103,22 +97,18 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
// efficient seeming though we already have the data in the inbox and outbox
|
||||
const activityObjectsMap = new Map<string, ObjectProperties>();
|
||||
|
||||
outboxActivities.forEach((activity) => {
|
||||
if (activity.object) {
|
||||
activityObjectsMap.set(activity.object.id, activity.object);
|
||||
}
|
||||
});
|
||||
inboxActivities.forEach((activity) => {
|
||||
activities.forEach((activity) => {
|
||||
if (activity.object) {
|
||||
activityObjectsMap.set(activity.object.id, activity.object);
|
||||
}
|
||||
});
|
||||
|
||||
// Filter the activities to show
|
||||
const activities = inboxActivities.filter((activity) => {
|
||||
// Only show "Create" activities that are replies to a post created
|
||||
// by the user
|
||||
activities = activities.filter((activity) => {
|
||||
if (activity.type === ACTVITY_TYPE.CREATE) {
|
||||
// Only show "Create" activities that are replies to a post created
|
||||
// by the user
|
||||
|
||||
const replyToObject = activityObjectsMap.get(activity.object?.inReplyTo || '');
|
||||
|
||||
// If the reply object is not found, or it doesn't have a URL or
|
||||
|
@ -138,10 +128,7 @@ const Activities: React.FC<ActivitiesProps> = ({}) => {
|
|||
}
|
||||
|
||||
return [ACTVITY_TYPE.FOLLOW, ACTVITY_TYPE.LIKE].includes(activity.type);
|
||||
})
|
||||
// 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
|
||||
// This allows us to quickly look up all comments for a given activity
|
||||
|
|
|
@ -17,7 +17,7 @@ const Inbox: React.FC<InboxProps> = ({}) => {
|
|||
const [layout, setLayout] = useState('inbox');
|
||||
|
||||
// Retrieve all activities for the user
|
||||
let {data: activities = []} = useAllActivitiesForUser('index');
|
||||
let {data: activities = []} = useAllActivitiesForUser({handle: 'index'});
|
||||
|
||||
activities = activities.filter((activity: Activity) => {
|
||||
const isCreate = activity.type === 'Create' && ['Article', 'Note'].includes(activity.object.type);
|
||||
|
|
|
@ -157,7 +157,6 @@ export function useFollowingForUser(handle: string) {
|
|||
export function useFollowersForUser(handle: string) {
|
||||
const siteUrl = useSiteUrl();
|
||||
const api = createActivityPubAPI(handle, siteUrl);
|
||||
|
||||
return useQuery({
|
||||
queryKey: [`followers:${handle}`],
|
||||
async queryFn() {
|
||||
|
@ -166,13 +165,13 @@ export function useFollowersForUser(handle: string) {
|
|||
});
|
||||
}
|
||||
|
||||
export function useAllActivitiesForUser(handle: string) {
|
||||
export function useAllActivitiesForUser({handle, includeOwn = false}: {handle: string, includeOwn?: boolean}) {
|
||||
const siteUrl = useSiteUrl();
|
||||
const api = createActivityPubAPI(handle, siteUrl);
|
||||
return useQuery({
|
||||
queryKey: [`activities:${handle}`],
|
||||
queryKey: [`activities:${handle}:includeOwn=${includeOwn.toString()}`],
|
||||
async queryFn() {
|
||||
return api.getAllActivities();
|
||||
return api.getAllActivities(includeOwn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue