Skip to content

Commit

Permalink
Moved webhook actions to API (#254)
Browse files Browse the repository at this point in the history
no refs

Moved webhook actions to API for easier maintenance and consistency with other
API actions
  • Loading branch information
mike182uk authored Jan 9, 2025
1 parent 219393f commit d2aaac4
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 151 deletions.
133 changes: 133 additions & 0 deletions src/api/action/webhook/post-published.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
type Actor,
Article,
Create,
Note,
PUBLIC_COLLECTION,
type RequestContext,
} from '@fedify/fedify';
import { Temporal } from '@js-temporal/polyfill';
import type { Context, Next } from 'hono';
import { v4 as uuidv4 } from 'uuid';
import { z } from 'zod';

import {
type ContextData,
type HonoContextVariables,
fedify,
} from '../../../app';
import { ACTOR_DEFAULT_HANDLE } from '../../../constants';
import { toURL } from '../../../helpers/uri';
import { addToList } from '../../../kv-helpers';

const PostSchema = z.object({
uuid: z.string().uuid(),
title: z.string(),
html: z.string().nullable(),
excerpt: z.string().nullable(),
feature_image: z.string().url().nullable(),
published_at: z.string().datetime(),
url: z.string().url(),
});

type Post = z.infer<typeof PostSchema>;

async function postToArticle(
ctx: RequestContext<ContextData>,
post: Post,
author: Actor | null,
) {
if (!post) {
return {
article: null,
preview: null,
};
}
const preview = new Note({
id: ctx.getObjectUri(Note, { id: post.uuid }),
content: post.excerpt,
});
const article = new Article({
id: ctx.getObjectUri(Article, { id: post.uuid }),
attribution: author,
name: post.title,
content: post.html,
image: toURL(post.feature_image),
published: Temporal.Instant.from(post.published_at),
preview: preview,
url: toURL(post.url),
to: PUBLIC_COLLECTION,
cc: ctx.getFollowersUri(ACTOR_DEFAULT_HANDLE),
});

return {
article,
preview,
};
}

const PostPublishedWebhookSchema = z.object({
post: z.object({
current: PostSchema,
}),
});

export async function webookPostPublishedAction(
ctx: Context<{ Variables: HonoContextVariables }>,
next: Next,
) {
const data = PostPublishedWebhookSchema.parse(
(await ctx.req.json()) as unknown,
);
const apCtx = fedify.createContext(ctx.req.raw as Request, {
db: ctx.get('db'),
globaldb: ctx.get('globaldb'),
logger: ctx.get('logger'),
});
const actor = await apCtx.getActor(ACTOR_DEFAULT_HANDLE);
const { article, preview } = await postToArticle(
apCtx,
data.post.current,
actor,
);
if (article) {
const create = new Create({
actor,
object: article,
id: apCtx.getObjectUri(Create, { id: uuidv4() }),
to: PUBLIC_COLLECTION,
cc: apCtx.getFollowersUri('index'),
});
try {
await article.toJsonLd();
await ctx
.get('globaldb')
.set([preview.id!.href], await preview.toJsonLd());
await ctx
.get('globaldb')
.set([create.id!.href], await create.toJsonLd());
await ctx
.get('globaldb')
.set([article.id!.href], await article.toJsonLd());
await addToList(ctx.get('db'), ['outbox'], create.id!.href);
await apCtx.sendActivity(
{ handle: ACTOR_DEFAULT_HANDLE },
'followers',
create,
{
preferSharedInbox: true,
},
);
} catch (err) {
ctx.get('logger').error('Post published webhook failed: {error}', {
error: err,
});
}
}
return new Response(JSON.stringify({}), {
headers: {
'Content-Type': 'application/activity+json',
},
status: 200,
});
}
35 changes: 35 additions & 0 deletions src/api/action/webhook/site-changed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Context } from 'hono';

import { type HonoContextVariables, fedify } from '../../../app';
import { updateSiteActor } from '../../../helpers/activitypub/actor';
import { getSiteSettings } from '../../../helpers/ghost';

export async function webhookSiteChangedAction(
ctx: Context<{ Variables: HonoContextVariables }>,
) {
try {
const host = ctx.req.header('host') || '';
const db = ctx.get('db');
const globaldb = ctx.get('globaldb');
const logger = ctx.get('logger');

const apCtx = fedify.createContext(ctx.req.raw as Request, {
db,
globaldb,
logger,
});

await updateSiteActor(apCtx, getSiteSettings);
} catch (err) {
ctx.get('logger').error('Site changed webhook failed: {error}', {
error: err,
});
}

return new Response(JSON.stringify({}), {
headers: {
'Content-Type': 'application/activity+json',
},
status: 200,
});
}
2 changes: 2 additions & 0 deletions src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export { profileGetFollowersAction } from './action/profile/get-followers';
export { profileGetFollowingAction } from './action/profile/get-following';
export { profileGetPostsAction } from './action/profile/get-posts';
export { searchAction } from './action/search';
export { webookPostPublishedAction } from './action/webhook/post-published';
export { webhookSiteChangedAction } from './action/webhook/site-changed';
8 changes: 4 additions & 4 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import {
profileGetFollowingAction,
profileGetPostsAction,
searchAction,
webhookSiteChangedAction,
webookPostPublishedAction,
} from './api';
import { client, getSite } from './db';
import {
Expand Down Expand Up @@ -82,9 +84,7 @@ import {
inboxHandler,
likeAction,
noteAction,
postPublishedWebhook,
replyAction,
siteChangedWebhook,
unlikeAction,
} from './handlers';
import { getTraceContext } from './helpers/context-header';
Expand Down Expand Up @@ -667,12 +667,12 @@ function validateWebhook() {
app.post(
'/.ghost/activitypub/webhooks/post/published',
validateWebhook(),
spanWrapper(postPublishedWebhook),
spanWrapper(webookPostPublishedAction),
);
app.post(
'/.ghost/activitypub/webhooks/site/changed',
validateWebhook(),
spanWrapper(siteChangedWebhook),
spanWrapper(webhookSiteChangedAction),
);

function requireRole(role: GhostRole) {
Expand Down
Loading

0 comments on commit d2aaac4

Please sign in to comment.