0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added posts to the user profile in admin-x-activitypub (#21369)

refs
[AP-484](https://linear.app/ghost/issue/AP-484/render-posts-on-user-profile)

Added posts to the user profile in admin-x-activitypub
This commit is contained in:
Michael Barrett 2024-10-22 19:54:05 +01:00 committed by GitHub
parent febb7f720e
commit b9768f99e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 209 additions and 4 deletions

View file

@ -182,6 +182,153 @@ describe('ActivityPubAPI', function () {
});
});
describe('getOutbox', function () {
test('It passes the token to the outbox endpoint', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
'https://activitypub.api/.ghost/activitypub/outbox/index': {
async assert(_resource, init) {
const headers = new Headers(init?.headers);
expect(headers.get('Authorization')).toContain('fake-token');
},
response: JSONResponse({
type: 'Collection',
items: []
})
}
});
const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);
await api.getOutbox();
});
test('Returns an empty array when the outbox is empty', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
'https://activitypub.api/.ghost/activitypub/outbox/index': {
response: JSONResponse({
type: 'Collection',
items: []
})
}
});
const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);
const actual = await api.getOutbox();
const expected: never[] = [];
expect(actual).toEqual(expected);
});
test('Returns all the items array when the outbox is not empty', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
'https://activitypub.api/.ghost/activitypub/outbox/index': {
response:
JSONResponse({
type: 'Collection',
orderedItems: [{
type: 'Create',
object: {
type: 'Note'
}
}]
})
}
});
const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);
const actual = await api.getOutbox();
const expected: Activity[] = [
{
type: 'Create',
object: {
type: 'Note'
}
}
];
expect(actual).toEqual(expected);
});
test('Returns an array when the orderedItems key is a single object', async function () {
const fakeFetch = Fetch({
'https://auth.api/': {
response: JSONResponse({
identities: [{
token: 'fake-token'
}]
})
},
'https://activitypub.api/.ghost/activitypub/outbox/index': {
response:
JSONResponse({
type: 'Collection',
orderedItems: {
type: 'Create',
object: {
type: 'Note'
}
}
})
}
});
const api = new ActivityPubAPI(
new URL('https://activitypub.api'),
new URL('https://auth.api'),
'index',
fakeFetch
);
const actual = await api.getOutbox();
const expected: Activity[] = [
{
type: 'Create',
object: {
type: 'Note'
}
}
];
expect(actual).toEqual(expected);
});
});
describe('getFollowing', function () {
test('It passes the token to the following endpoint', async function () {
const fakeFetch = Fetch({

View file

@ -86,6 +86,24 @@ 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);
}

View file

@ -12,6 +12,7 @@ import {
useFollowingCountForUser,
useFollowingForUser,
useLikedForUser,
useOutboxForUser,
useUserDataForUser
} from '../hooks/useActivityPubQueries';
@ -23,6 +24,8 @@ const Profile: React.FC<ProfileProps> = ({}) => {
const {data: following = []} = useFollowingForUser('index');
const {data: followers = []} = useFollowersForUser('index');
const {data: liked = []} = useLikedForUser('index');
const {data: posts = []} = useOutboxForUser('index');
// Replace 'index' with the actual handle of the user
const {data: userProfile} = useUserDataForUser('index') as {data: ActorProperties | null};
@ -36,10 +39,36 @@ const Profile: React.FC<ProfileProps> = ({}) => {
{
id: 'posts',
title: 'Posts',
contents: (<div><NoValueLabel icon='pen'>
You haven&apos;t posted anything yet.
</NoValueLabel></div>),
counter: 240
contents: (
<div className='ap-posts'>
{posts.length === 0 ? (
<NoValueLabel icon='pen'>
You haven&apos;t posted anything yet.
</NoValueLabel>
) : (
<ul className='mx-auto flex max-w-[640px] flex-col'>
{posts.map((activity, index) => (
<li
key={activity.id}
data-test-view-article
>
<FeedItem
actor={activity.object?.attributedTo || activity.actor}
layout={layout}
object={activity.object}
type={activity.type}
onCommentClick={() => {}}
/>
{index < posts.length - 1 && (
<div className="h-px w-full bg-grey-200"></div>
)}
</li>
))}
</ul>
)}
</div>
),
counter: posts.length
},
{
id: 'likes',

View file

@ -362,3 +362,14 @@ export function useProfileForUser(handle: string, fullHandle: string) {
}
});
}
export function useOutboxForUser(handle: string) {
return useQuery({
queryKey: [`outbox:${handle}`],
async queryFn() {
const siteUrl = await getSiteUrl();
const api = createActivityPubAPI(handle, siteUrl);
return api.getOutbox();
}
});
}