Loading examples/blog/federation/mod.ts +12 −13 Original line number Diff line number Diff line import { Federation } from "fedify/federation/middleware.ts"; import { isActor } from "fedify/vocab/actor.ts"; import { getActorTypeName, isActor } from "fedify/vocab/actor.ts"; import { Accept, Activity, Create, Follow, Note, Link, Person, Undo, } from "fedify/vocab/mod.ts"; import { getBlog } from "../models/blog.ts"; import { addFollower, removeFollower } from "../models/follower.ts"; import { openKv } from "../models/kv.ts"; import { countPosts, getPosts } from "../models/post.ts"; import { addFollower } from "fedify/examples/blog/models/follower.ts"; import { Link } from "fedify/vocab/mod.ts"; import { removeFollower } from "fedify/examples/blog/models/follower.ts"; import { countPosts, getPosts, toNote } from "../models/post.ts"; // The `Federation<TContextData>` object is a registry that registers // federation-related callbacks: Loading Loading @@ -69,13 +67,10 @@ federation.setOutboxDispatcher( ); for await (const post of posts) { const activity = new Create({ id: new URL(`/posts/${post.uuid}#activity`, ctx.request.url), actor: ctx.getActorUri(handle), object: new Note({ attributedTo: ctx.getActorUri(handle), content: post.content, published: post.published, url: new URL(`/posts/${post.uuid}`, ctx.request.url), }), to: new URL("https://www.w3.org/ns/activitystreams#Public"), object: toNote(ctx, blog, post), }); activities.push(activity); } Loading Loading @@ -115,7 +110,8 @@ federation.setInboxListeners("/users/{handle}/inbox") const recipient = await follow.getActor(ctx); if ( !isActor(recipient) || recipient.id == null || recipient.preferredUsername == null recipient.preferredUsername == null || recipient.inboxId == null ) return; const handle = `@${recipient.preferredUsername.toString()}@${recipient.id.host}`; Loading @@ -129,6 +125,9 @@ federation.setInboxListeners("/users/{handle}/inbox") ? (recipient.url.href ?? recipient.id).href : recipient.url.href, handle, inbox: recipient.inboxId.href, sharedInbox: recipient.endpoints?.sharedInbox?.href, typeName: getActorTypeName(recipient), }); await ctx.sendActivity( { handle: blog.handle }, Loading examples/blog/models/follower.ts +28 −0 Original line number Diff line number Diff line import { Actor, ActorTypeName, getActorClassByTypeName, } from "fedify/vocab/actor.ts"; import { Endpoints } from "fedify/vocab/mod.ts"; import { openKv } from "./kv.ts"; export interface Follower { Loading @@ -6,6 +12,9 @@ export interface Follower { name: string; url: string; handle: string; inbox: string; sharedInbox?: string; typeName: ActorTypeName; } export async function addFollower(follower: Follower): Promise<Follower> { Loading Loading @@ -56,3 +65,22 @@ export async function countFollowers(): Promise<bigint> { const record = await kv.get(["followers"]); return (record?.value as bigint | null) ?? 0n; } export async function getFollowersAsActors(): Promise<Actor[]> { const kv = await openKv(); const actors: Actor[] = []; for await (const f of kv.list<Follower>({ prefix: ["follower"] })) { const cls = getActorClassByTypeName(f.value.typeName); const actor = new cls({ id: new URL(f.value.id), inbox: new URL(f.value.inbox), endpoints: new Endpoints({ sharedInbox: f.value.sharedInbox ? new URL(f.value.sharedInbox) : undefined, }), }); actors.push(actor); } return actors; } examples/blog/models/post.ts +20 −0 Original line number Diff line number Diff line import { RequestContext } from "fedify/federation/context.ts"; import { Note } from "fedify/vocab/mod.ts"; import markdownIt from "markdown-it"; import { uuidv7 } from "uuidv7"; import { Blog } from "./blog.ts"; import { openKv } from "./kv.ts"; export interface Post { Loading Loading @@ -65,3 +68,20 @@ export function getContentHtml(post: Post): string { const md = markdownIt(); return md.render(post.content); } export function toNote( context: RequestContext<void>, blog: Blog, post: Post, ): Note { const url = new URL(`/posts/${post.uuid}`, context.request.url); return new Note({ id: url, attributedTo: context.getActorUri(blog.handle), to: new URL("https://www.w3.org/ns/activitystreams#Public"), summary: post.title, content: post.content, published: post.published, url, }); } examples/blog/routes/posts/[uuid].tsx +21 −1 Original line number Diff line number Diff line import { Handler, PageProps } from "$fresh/server.ts"; import { Head } from "$fresh/runtime.ts"; import { accepts } from "$std/http/mod.ts"; import Post from "../../components/Post.tsx"; import { federation } from "../../federation/mod.ts"; import { Blog, getBlog } from "../../models/blog.ts"; import { getPost, type Post as PostModel } from "../../models/post.ts"; import { getPost, type Post as PostModel, toNote } from "../../models/post.ts"; export interface PostPageData { domain: string; Loading @@ -15,6 +17,24 @@ export const handler: Handler<PostPageData> = async (req, ctx) => { if (blog == null) return await ctx.renderNotFound(); const post = await getPost(ctx.params.uuid); if (post == null) return await ctx.renderNotFound(); const accept = accepts( req, "application/ld+json", "application/json", "text/html", "application/xhtml+xml", ); if (accept == "application/ld+json" || accept == "application/json") { const fedCtx = federation.createContext(req); const note = toNote(fedCtx, blog, post); const jsonLd = await note.toJsonLd(fedCtx); return new Response(JSON.stringify(jsonLd), { headers: { "Content-Type": "application/ld+json", Vary: "Accept", }, }); } const data: PostPageData = { blog, post, domain: ctx.url.host }; return ctx.render(data); }; Loading examples/blog/routes/posts/index.tsx +28 −4 Original line number Diff line number Diff line import { Handlers, PageProps } from "$fresh/server.ts"; import { Blog, getBlog, verifyPassword } from "../../models/blog.ts"; import { countFollowers } from "../../models/follower.ts"; import { addPost, countPosts, getPosts, Post } from "../../models/post.ts"; import { getFollowersAsActors } from "fedify/examples/blog/models/follower.ts"; import { Create, Note } from "fedify/vocab/mod.ts"; import { PostFormProps } from "../../components/PostForm.tsx"; import PostList from "../../components/PostList.tsx"; import { federation } from "../../federation/mod.ts"; import { Blog, getBlog, verifyPassword } from "../../models/blog.ts"; import { countFollowers } from "../../models/follower.ts"; import { addPost, countPosts, getPosts, Post, toNote, } from "../../models/post.ts"; interface PostsData extends PostFormProps { blog: Blog; Loading Loading @@ -67,7 +76,22 @@ export const handler: Handlers<PostsData> = { defaultValues: { title, content }, }); } await addPost({ title, content, published: Temporal.Now.instant() }); const post = await addPost({ title, content, published: Temporal.Now.instant(), }); const fedCtx = await federation.createContext(req); await fedCtx.sendActivity( { handle: blog.handle }, await getFollowersAsActors(), new Create({ id: new URL(`/posts/${post.uuid}#activity`, req.url), actor: fedCtx.getActorUri(blog.handle), to: new URL("https://www.w3.org/ns/activitystreams#Public"), object: toNote(fedCtx, blog, post), }), ); const { posts, nextCursor } = await getPosts(); const total = await countPosts(); Loading Loading
examples/blog/federation/mod.ts +12 −13 Original line number Diff line number Diff line import { Federation } from "fedify/federation/middleware.ts"; import { isActor } from "fedify/vocab/actor.ts"; import { getActorTypeName, isActor } from "fedify/vocab/actor.ts"; import { Accept, Activity, Create, Follow, Note, Link, Person, Undo, } from "fedify/vocab/mod.ts"; import { getBlog } from "../models/blog.ts"; import { addFollower, removeFollower } from "../models/follower.ts"; import { openKv } from "../models/kv.ts"; import { countPosts, getPosts } from "../models/post.ts"; import { addFollower } from "fedify/examples/blog/models/follower.ts"; import { Link } from "fedify/vocab/mod.ts"; import { removeFollower } from "fedify/examples/blog/models/follower.ts"; import { countPosts, getPosts, toNote } from "../models/post.ts"; // The `Federation<TContextData>` object is a registry that registers // federation-related callbacks: Loading Loading @@ -69,13 +67,10 @@ federation.setOutboxDispatcher( ); for await (const post of posts) { const activity = new Create({ id: new URL(`/posts/${post.uuid}#activity`, ctx.request.url), actor: ctx.getActorUri(handle), object: new Note({ attributedTo: ctx.getActorUri(handle), content: post.content, published: post.published, url: new URL(`/posts/${post.uuid}`, ctx.request.url), }), to: new URL("https://www.w3.org/ns/activitystreams#Public"), object: toNote(ctx, blog, post), }); activities.push(activity); } Loading Loading @@ -115,7 +110,8 @@ federation.setInboxListeners("/users/{handle}/inbox") const recipient = await follow.getActor(ctx); if ( !isActor(recipient) || recipient.id == null || recipient.preferredUsername == null recipient.preferredUsername == null || recipient.inboxId == null ) return; const handle = `@${recipient.preferredUsername.toString()}@${recipient.id.host}`; Loading @@ -129,6 +125,9 @@ federation.setInboxListeners("/users/{handle}/inbox") ? (recipient.url.href ?? recipient.id).href : recipient.url.href, handle, inbox: recipient.inboxId.href, sharedInbox: recipient.endpoints?.sharedInbox?.href, typeName: getActorTypeName(recipient), }); await ctx.sendActivity( { handle: blog.handle }, Loading
examples/blog/models/follower.ts +28 −0 Original line number Diff line number Diff line import { Actor, ActorTypeName, getActorClassByTypeName, } from "fedify/vocab/actor.ts"; import { Endpoints } from "fedify/vocab/mod.ts"; import { openKv } from "./kv.ts"; export interface Follower { Loading @@ -6,6 +12,9 @@ export interface Follower { name: string; url: string; handle: string; inbox: string; sharedInbox?: string; typeName: ActorTypeName; } export async function addFollower(follower: Follower): Promise<Follower> { Loading Loading @@ -56,3 +65,22 @@ export async function countFollowers(): Promise<bigint> { const record = await kv.get(["followers"]); return (record?.value as bigint | null) ?? 0n; } export async function getFollowersAsActors(): Promise<Actor[]> { const kv = await openKv(); const actors: Actor[] = []; for await (const f of kv.list<Follower>({ prefix: ["follower"] })) { const cls = getActorClassByTypeName(f.value.typeName); const actor = new cls({ id: new URL(f.value.id), inbox: new URL(f.value.inbox), endpoints: new Endpoints({ sharedInbox: f.value.sharedInbox ? new URL(f.value.sharedInbox) : undefined, }), }); actors.push(actor); } return actors; }
examples/blog/models/post.ts +20 −0 Original line number Diff line number Diff line import { RequestContext } from "fedify/federation/context.ts"; import { Note } from "fedify/vocab/mod.ts"; import markdownIt from "markdown-it"; import { uuidv7 } from "uuidv7"; import { Blog } from "./blog.ts"; import { openKv } from "./kv.ts"; export interface Post { Loading Loading @@ -65,3 +68,20 @@ export function getContentHtml(post: Post): string { const md = markdownIt(); return md.render(post.content); } export function toNote( context: RequestContext<void>, blog: Blog, post: Post, ): Note { const url = new URL(`/posts/${post.uuid}`, context.request.url); return new Note({ id: url, attributedTo: context.getActorUri(blog.handle), to: new URL("https://www.w3.org/ns/activitystreams#Public"), summary: post.title, content: post.content, published: post.published, url, }); }
examples/blog/routes/posts/[uuid].tsx +21 −1 Original line number Diff line number Diff line import { Handler, PageProps } from "$fresh/server.ts"; import { Head } from "$fresh/runtime.ts"; import { accepts } from "$std/http/mod.ts"; import Post from "../../components/Post.tsx"; import { federation } from "../../federation/mod.ts"; import { Blog, getBlog } from "../../models/blog.ts"; import { getPost, type Post as PostModel } from "../../models/post.ts"; import { getPost, type Post as PostModel, toNote } from "../../models/post.ts"; export interface PostPageData { domain: string; Loading @@ -15,6 +17,24 @@ export const handler: Handler<PostPageData> = async (req, ctx) => { if (blog == null) return await ctx.renderNotFound(); const post = await getPost(ctx.params.uuid); if (post == null) return await ctx.renderNotFound(); const accept = accepts( req, "application/ld+json", "application/json", "text/html", "application/xhtml+xml", ); if (accept == "application/ld+json" || accept == "application/json") { const fedCtx = federation.createContext(req); const note = toNote(fedCtx, blog, post); const jsonLd = await note.toJsonLd(fedCtx); return new Response(JSON.stringify(jsonLd), { headers: { "Content-Type": "application/ld+json", Vary: "Accept", }, }); } const data: PostPageData = { blog, post, domain: ctx.url.host }; return ctx.render(data); }; Loading
examples/blog/routes/posts/index.tsx +28 −4 Original line number Diff line number Diff line import { Handlers, PageProps } from "$fresh/server.ts"; import { Blog, getBlog, verifyPassword } from "../../models/blog.ts"; import { countFollowers } from "../../models/follower.ts"; import { addPost, countPosts, getPosts, Post } from "../../models/post.ts"; import { getFollowersAsActors } from "fedify/examples/blog/models/follower.ts"; import { Create, Note } from "fedify/vocab/mod.ts"; import { PostFormProps } from "../../components/PostForm.tsx"; import PostList from "../../components/PostList.tsx"; import { federation } from "../../federation/mod.ts"; import { Blog, getBlog, verifyPassword } from "../../models/blog.ts"; import { countFollowers } from "../../models/follower.ts"; import { addPost, countPosts, getPosts, Post, toNote, } from "../../models/post.ts"; interface PostsData extends PostFormProps { blog: Blog; Loading Loading @@ -67,7 +76,22 @@ export const handler: Handlers<PostsData> = { defaultValues: { title, content }, }); } await addPost({ title, content, published: Temporal.Now.instant() }); const post = await addPost({ title, content, published: Temporal.Now.instant(), }); const fedCtx = await federation.createContext(req); await fedCtx.sendActivity( { handle: blog.handle }, await getFollowersAsActors(), new Create({ id: new URL(`/posts/${post.uuid}#activity`, req.url), actor: fedCtx.getActorUri(blog.handle), to: new URL("https://www.w3.org/ns/activitystreams#Public"), object: toNote(fedCtx, blog, post), }), ); const { posts, nextCursor } = await getPosts(); const total = await countPosts(); Loading