Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated activities tab to use the activities endpoint in the activitypub app #21037

Merged
merged 1 commit into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 0 additions & 17 deletions apps/admin-x-activitypub/src/MainContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
36 changes: 34 additions & 2 deletions apps/admin-x-activitypub/src/api/activitypub.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
});
});
});
22 changes: 3 additions & 19 deletions apps/admin-x-activitypub/src/api/activitypub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,6 @@ export class ActivityPubAPI {
return [];
}

get outboxApiUrl() {
return new URL(`.ghost/activitypub/outbox/${this.handle}`, this.apiUrl);
}

async getOutbox(): Promise<Activity[]> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need this as we can pass includeOwn when making a request to the activities endpoint

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);
}
Expand Down Expand Up @@ -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[]> => {
Expand All @@ -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);

Expand All @@ -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);
Expand Down
31 changes: 9 additions & 22 deletions apps/admin-x-activitypub/src/components/Activities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}

Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endpoint returns newest first

});

// 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
Expand Down
2 changes: 1 addition & 1 deletion apps/admin-x-activitypub/src/components/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 3 additions & 4 deletions apps/admin-x-activitypub/src/hooks/useActivityPubQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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()}`],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we have a better scheme for this 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine for now!

async queryFn() {
return api.getAllActivities();
return api.getAllActivities(includeOwn);
}
});
}
Loading