Skip to content

Commit a186909

Browse files
authored
Refactored createGetAccountPostsHandler | Moved posts and liked post queries to AccountPostView (#544)
Ref https://linear.app/ghost/issue/AP-1024 - Moved posts and liked post queries from PostService to AccountPostView - Added logic to fetch post for local and remote accounts to AccountPostView - Refactored createGetAccountPostsHandler to use AccountPostView - Added tests for new AccountPostView
1 parent a514dbe commit a186909

File tree

8 files changed

+1198
-868
lines changed

8 files changed

+1198
-868
lines changed

src/account/account.entity.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { BaseEntity } from '../core/base.entity';
33
import { type CreatePostType, PostType } from '../post/post.entity';
44
import type { Site } from '../site/site.service';
55

6+
export type PersistedAccount = Account & { id: number };
7+
68
export interface AccountData {
79
id: number;
810
uuid: string | null;

src/app.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ import {
114114
handleCreateNote,
115115
} from './http/api';
116116
import { AccountFollowsView } from './http/api/views/account.follows.view';
117+
import { AccountPostsView } from './http/api/views/account.posts.view';
117118
import { createWebFingerHandler } from './http/handler/webfinger';
118119
import { spanWrapper } from './instrumentation';
119120
import { KnexKvStore } from './knex.kvstore';
@@ -272,6 +273,7 @@ const postService = new PostService(
272273
);
273274

274275
const accountFollowsView = new AccountFollowsView(client, fedifyContextFactory);
276+
const accountPostsView = new AccountPostsView(client, fedifyContextFactory);
275277
const siteService = new SiteService(client, accountService, {
276278
getSiteSettings: getSiteSettings,
277279
});
@@ -998,13 +1000,19 @@ app.get(
9981000
'/.ghost/activitypub/posts/:handle',
9991001
requireRole(GhostRole.Owner, GhostRole.Administrator),
10001002
spanWrapper(
1001-
createGetAccountPostsHandler(postService, accountRepository, fedify),
1003+
createGetAccountPostsHandler(
1004+
accountRepository,
1005+
accountPostsView,
1006+
fedifyContextFactory,
1007+
),
10021008
),
10031009
);
10041010
app.get(
10051011
'/.ghost/activitypub/posts/:handle/liked',
10061012
requireRole(GhostRole.Owner, GhostRole.Administrator),
1007-
spanWrapper(createGetAccountLikedPostsHandler(accountService, postService)),
1013+
spanWrapper(
1014+
createGetAccountLikedPostsHandler(accountService, accountPostsView),
1015+
),
10081016
);
10091017
app.get(
10101018
'/.ghost/activitypub/account/:handle/follows/:type',

src/http/api/account.ts

Lines changed: 68 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
import type { Federation } from '@fedify/fedify';
2-
import type { Account } from 'account/account.entity';
2+
import type { Account, PersistedAccount } from 'account/account.entity';
33
import type { KnexAccountRepository } from 'account/account.repository.knex';
44
import type { AccountService } from 'account/account.service';
55
import type { FedifyContextFactory } from 'activitypub/fedify-context.factory';
66
import type { AppContext, ContextData } from 'app';
77
import { exhaustiveCheck, getError, getValue, isError } from 'core/result';
88
import { isHandle } from 'helpers/activitypub/actor';
99
import { lookupAPIdByHandle } from 'lookup-helpers';
10-
import type { GetProfileDataResult, PostService } from 'post/post.service';
1110
import { z } from 'zod';
1211
import {
1312
getAccountDTOByHandle,
@@ -18,7 +17,10 @@ import type {
1817
AccountFollows,
1918
AccountFollowsView,
2019
} from './views/account.follows.view';
21-
20+
import type {
21+
AccountPosts,
22+
AccountPostsView,
23+
} from './views/account.posts.view';
2224
/**
2325
* Default number of posts to return in a profile
2426
*/
@@ -218,9 +220,9 @@ function validateRequestParams(ctx: AppContext) {
218220
* @param profileService Profile service instance
219221
*/
220222
export function createGetAccountPostsHandler(
221-
postService: PostService,
222223
accountRepository: KnexAccountRepository,
223-
fedify: Federation<ContextData>,
224+
accountPostsView: AccountPostsView,
225+
fedifyContextFactory: FedifyContextFactory,
224226
) {
225227
/**
226228
* Handle a request for a list of posts by an account
@@ -234,119 +236,86 @@ export function createGetAccountPostsHandler(
234236
}
235237

236238
const logger = ctx.get('logger');
237-
let account: Account | null = null;
238-
const db = ctx.get('db');
239-
240-
const apCtx = fedify.createContext(ctx.req.raw as Request, {
241-
db,
242-
globaldb: ctx.get('globaldb'),
243-
logger,
244-
});
239+
const site = ctx.get('site');
245240

246241
const handle = ctx.req.param('handle');
247242
if (!handle) {
248243
return new Response(null, { status: 400 });
249244
}
250245

251-
const defaultAccount = await accountRepository.getBySite(
252-
ctx.get('site'),
253-
);
246+
const currentContextAccount = (await accountRepository.getBySite(
247+
site,
248+
)) as PersistedAccount;
254249

255-
if (!defaultAccount || !defaultAccount.id) {
256-
return new Response(null, { status: 400 });
257-
}
250+
let accountPosts: AccountPosts;
258251

259252
// We are using the keyword 'me', if we want to get the posts of the current user
260253
if (handle === 'me') {
261-
account = defaultAccount;
254+
accountPosts = await accountPostsView.getPostsByAccount(
255+
currentContextAccount.id,
256+
currentContextAccount.id,
257+
params.limit,
258+
params.cursor,
259+
);
262260
} else {
263-
if (!isHandle(handle)) {
264-
return new Response(null, { status: 400 });
265-
}
261+
const ctx = fedifyContextFactory.getFedifyContext();
262+
const apId = await lookupAPIdByHandle(ctx, handle);
266263

267-
const apId = await lookupAPIdByHandle(apCtx, handle);
268-
if (apId) {
269-
account = await accountRepository.getByApId(new URL(apId));
264+
if (!apId) {
265+
return new Response(`AP ID not found for handle: ${handle}`, {
266+
status: 400,
267+
});
270268
}
271-
}
272-
273-
const result: GetProfileDataResult = {
274-
results: [],
275-
nextCursor: null,
276-
};
277269

278-
try {
279-
//If we found the account in our db and it's an internal account, do an internal lookup
280-
if (account?.isInternal && account.id) {
281-
const postResult = await postService.getPostsByAccount(
282-
account.id,
283-
defaultAccount.id,
284-
params.limit,
285-
params.cursor,
286-
);
270+
const account = (await accountRepository.getByApId(
271+
new URL(apId),
272+
)) as PersistedAccount;
287273

288-
result.results = postResult.results;
289-
result.nextCursor = postResult.nextCursor;
290-
} else {
291-
//Otherwise, do a remote lookup to fetch the posts
292-
const postResult = await postService.getPostsByRemoteLookUp(
293-
defaultAccount.id,
294-
defaultAccount.apId,
295-
handle,
296-
params.cursor || '',
297-
);
298-
if (postResult instanceof Error) {
299-
throw postResult;
300-
}
301-
if (isError(postResult)) {
302-
const error = getError(postResult);
303-
switch (error) {
304-
case 'invalid-next-parameter':
305-
logger.error('Invalid next parameter');
306-
return new Response(null, { status: 400 });
307-
case 'not-an-actor':
308-
logger.error(`Actor not found for ${handle}`);
309-
return new Response(null, { status: 404 });
310-
case 'error-getting-outbox':
311-
logger.error(`Error getting outbox for ${handle}`);
312-
return new Response(
313-
JSON.stringify({
314-
posts: [],
315-
next: null,
316-
}),
317-
{ status: 200 },
318-
);
319-
case 'no-page-found':
320-
logger.error(
321-
`No page found in outbox for ${handle}`,
322-
);
323-
return new Response(
324-
JSON.stringify({
325-
posts: [],
326-
next: null,
327-
}),
328-
{ status: 200 },
329-
);
330-
default:
331-
return exhaustiveCheck(error);
332-
}
274+
const result = await accountPostsView.getPostsByApId(
275+
new URL(apId),
276+
account,
277+
currentContextAccount,
278+
params.limit,
279+
params.cursor,
280+
);
281+
if (isError(result)) {
282+
const error = getError(result);
283+
switch (error) {
284+
case 'invalid-next-parameter':
285+
logger.error('Invalid next parameter');
286+
return new Response(null, { status: 400 });
287+
case 'not-an-actor':
288+
logger.error(`Actor not found for ${handle}`);
289+
return new Response(null, { status: 404 });
290+
case 'error-getting-outbox':
291+
logger.error(`Error getting outbox for ${handle}`);
292+
return new Response(
293+
JSON.stringify({
294+
posts: [],
295+
next: null,
296+
}),
297+
{ status: 200 },
298+
);
299+
case 'no-page-found':
300+
logger.error(`No page found in outbox for ${handle}`);
301+
return new Response(
302+
JSON.stringify({
303+
posts: [],
304+
next: null,
305+
}),
306+
{ status: 200 },
307+
);
308+
default:
309+
return exhaustiveCheck(error);
333310
}
334-
const posts = getValue(postResult);
335-
result.results = posts.results;
336-
result.nextCursor = posts.nextCursor;
337311
}
338-
} catch (error) {
339-
logger.error(`Error getting posts for ${handle}: {error}`, {
340-
error,
341-
});
342-
343-
return new Response(null, { status: 500 });
312+
accountPosts = getValue(result);
344313
}
345314

346315
return new Response(
347316
JSON.stringify({
348-
posts: result.results,
349-
next: result.nextCursor,
317+
posts: accountPosts.results,
318+
next: accountPosts.nextCursor,
350319
}),
351320
{ status: 200 },
352321
);
@@ -361,7 +330,7 @@ export function createGetAccountPostsHandler(
361330
*/
362331
export function createGetAccountLikedPostsHandler(
363332
accountService: AccountService,
364-
postService: PostService,
333+
accountPostsView: AccountPostsView,
365334
) {
366335
/**
367336
* Handle a request for a list of posts liked by an account
@@ -383,7 +352,7 @@ export function createGetAccountLikedPostsHandler(
383352
}
384353

385354
const { results, nextCursor } =
386-
await postService.getPostsLikedByAccount(
355+
await accountPostsView.getPostsLikedByAccount(
387356
account.id,
388357
params.limit,
389358
params.cursor,

0 commit comments

Comments
 (0)