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

Refactored collection retrieval in admin-x-activitypub (#21491)

no refs

Refactored collection retrieval to reduce code duplication and
complexity
This commit is contained in:
Michael Barrett 2024-10-31 20:31:20 +00:00 committed by GitHub
parent 4c79887b79
commit b90e16219d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 48 additions and 137 deletions

View file

@ -183,7 +183,7 @@ describe('ActivityPubAPI', function () {
}); });
describe('getOutbox', function () { describe('getOutbox', function () {
test('It passes the token to the outbox endpoint', async function () { test('It passes the token to the outbox collection endpoint', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -213,7 +213,7 @@ describe('ActivityPubAPI', function () {
await api.getOutbox(); await api.getOutbox();
}); });
test('Returns an empty array when the outbox is empty', async function () { test('Returns an empty array when the outbox collection is empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -247,7 +247,7 @@ describe('ActivityPubAPI', function () {
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
test('Recursively retrieves all items and returns them when the outbox is not empty', async function () { test('Recursively retrieves all items and returns them when the outbox collection is not empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -316,7 +316,7 @@ describe('ActivityPubAPI', function () {
}); });
describe('getFollowing', function () { describe('getFollowing', function () {
test('It passes the token to the following endpoint', async function () { test('It passes the token to the following collection endpoint', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -346,7 +346,7 @@ describe('ActivityPubAPI', function () {
await api.getFollowing(); await api.getFollowing();
}); });
test('Returns an empty array when the following is empty', async function () { test('Returns an empty array when the following collection is empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -380,7 +380,7 @@ describe('ActivityPubAPI', function () {
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
test('Recursively retrieves all items and returns them when the following is not empty', async function () { test('Recursively retrieves all items and returns them when the following collection is not empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -437,7 +437,7 @@ describe('ActivityPubAPI', function () {
}); });
describe('getFollowers', function () { describe('getFollowers', function () {
test('It passes the token to the following endpoint', async function () { test('It passes the token to the followers collection endpoint', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -467,7 +467,7 @@ describe('ActivityPubAPI', function () {
await api.getFollowers(); await api.getFollowers();
}); });
test('Returns an empty array when the followers is empty', async function () { test('Returns an empty array when the followers collection is empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({
@ -501,7 +501,7 @@ describe('ActivityPubAPI', function () {
expect(actual).toEqual(expected); expect(actual).toEqual(expected);
}); });
test('Recursively retrieves all items and returns them when the followers is not empty', async function () { test('Recursively retrieves all items and returns them when the followers collection is not empty', async function () {
const fakeFetch = Fetch({ const fakeFetch = Fetch({
'https://auth.api/': { 'https://auth.api/': {
response: JSONResponse({ response: JSONResponse({

View file

@ -73,6 +73,41 @@ export class ActivityPubAPI {
return json; return json;
} }
private async getActivityPubCollection<T>(collectionUrl: URL): Promise<T[]> {
const fetchPage = async (pageUrl: URL): Promise<T[]> => {
const json = await this.fetchJSON(pageUrl);
if (json === null) {
return [];
}
let items: T[] = [];
if ('orderedItems' in json) {
items = Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
}
if ('next' in json && typeof json.next === 'string') {
const nextPageUrl = new URL(json.next);
const nextPageItems = await fetchPage(nextPageUrl);
items = items.concat(nextPageItems);
}
return items;
};
const initialJson = await this.fetchJSON(collectionUrl);
if (initialJson === null || !('first' in initialJson) || typeof initialJson.first !== 'string') {
return [];
}
const firstPageUrl = new URL(initialJson.first);
return fetchPage(firstPageUrl);
}
get inboxApiUrl() { get inboxApiUrl() {
return new URL(`.ghost/activitypub/inbox/${this.handle}`, this.apiUrl); return new URL(`.ghost/activitypub/inbox/${this.handle}`, this.apiUrl);
} }
@ -96,38 +131,7 @@ export class ActivityPubAPI {
} }
async getOutbox(): Promise<Activity[]> { async getOutbox(): Promise<Activity[]> {
const fetchOutboxPage = async (url: URL): Promise<Activity[]> => { return this.getActivityPubCollection<Activity>(this.outboxApiUrl);
const json = await this.fetchJSON(url);
if (json === null) {
return [];
}
let items: Activity[] = [];
if ('orderedItems' in json) {
items = Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
}
if ('next' in json && typeof json.next === 'string') {
const nextUrl = new URL(json.next);
const nextItems = await fetchOutboxPage(nextUrl);
items = items.concat(nextItems);
}
return items;
};
const initialJson = await this.fetchJSON(this.outboxApiUrl);
if (initialJson === null || !('first' in initialJson) || typeof initialJson.first !== 'string') {
return [];
}
const firstPageUrl = new URL(initialJson.first);
return fetchOutboxPage(firstPageUrl);
} }
get followingApiUrl() { get followingApiUrl() {
@ -135,38 +139,7 @@ export class ActivityPubAPI {
} }
async getFollowing(): Promise<Actor[]> { async getFollowing(): Promise<Actor[]> {
const fetchFollowingPage = async (url: URL): Promise<Actor[]> => { return this.getActivityPubCollection<Actor>(this.followingApiUrl);
const json = await this.fetchJSON(url);
if (json === null) {
return [];
}
let items: Actor[] = [];
if ('orderedItems' in json) {
items = Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
}
if ('next' in json && typeof json.next === 'string') {
const nextUrl = new URL(json.next);
const nextItems = await fetchFollowingPage(nextUrl);
items = items.concat(nextItems);
}
return items;
};
const initialJson = await this.fetchJSON(this.followingApiUrl);
if (initialJson === null || !('first' in initialJson) || typeof initialJson.first !== 'string') {
return [];
}
const firstPageUrl = new URL(initialJson.first);
return fetchFollowingPage(firstPageUrl);
} }
async getFollowingCount(): Promise<number> { async getFollowingCount(): Promise<number> {
@ -185,38 +158,7 @@ export class ActivityPubAPI {
} }
async getFollowers(): Promise<Actor[]> { async getFollowers(): Promise<Actor[]> {
const fetchFollowersPage = async (url: URL): Promise<Actor[]> => { return this.getActivityPubCollection<Actor>(this.followersApiUrl);
const json = await this.fetchJSON(url);
if (json === null) {
return [];
}
let items: Actor[] = [];
if ('orderedItems' in json) {
items = Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
}
if ('next' in json && typeof json.next === 'string') {
const nextUrl = new URL(json.next);
const nextItems = await fetchFollowersPage(nextUrl);
items = items.concat(nextItems);
}
return items;
};
const initialJson = await this.fetchJSON(this.followersApiUrl);
if (initialJson === null || !('first' in initialJson) || typeof initialJson.first !== 'string') {
return [];
}
const firstPageUrl = new URL(initialJson.first);
return fetchFollowersPage(firstPageUrl);
} }
async getFollowersCount(): Promise<number> { async getFollowersCount(): Promise<number> {
@ -307,38 +249,7 @@ export class ActivityPubAPI {
} }
async getLiked() { async getLiked() {
const fetchLikedPage = async (url: URL): Promise<Activity[]> => { return this.getActivityPubCollection<Activity>(this.likedApiUrl);
const json = await this.fetchJSON(url);
if (json === null) {
return [];
}
let items: Activity[] = [];
if ('orderedItems' in json) {
items = Array.isArray(json.orderedItems) ? json.orderedItems : [json.orderedItems];
}
if ('next' in json && typeof json.next === 'string') {
const nextUrl = new URL(json.next);
const nextItems = await fetchLikedPage(nextUrl);
items = items.concat(nextItems);
}
return items;
};
const initialJson = await this.fetchJSON(this.likedApiUrl);
if (initialJson === null || !('first' in initialJson) || typeof initialJson.first !== 'string') {
return [];
}
const firstPageUrl = new URL(initialJson.first);
return fetchLikedPage(firstPageUrl);
} }
async like(id: string): Promise<void> { async like(id: string): Promise<void> {