Loading examples/blog/components/PostList.tsx +7 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ export interface PostListProps extends PostFormProps { blog: Blog; posts: PostModel[]; total: bigint; followers: bigint; nextCursor: string | null; domain: string; } Loading @@ -17,6 +18,7 @@ export default function PostList( blog, posts, total, followers, nextCursor, domain, error, Loading @@ -32,7 +34,11 @@ export default function PostList( <hgroup> <h1>{blog.title}</h1> <p> <strong>@{blog.handle}@{domain}</strong> · {blog.description} <strong>@{blog.handle}@{domain}</strong> ·{" "} <a href="/followers"> {followers === 1n ? "1 follower" : `${followers} followers`} </a>{" "} · {blog.description} </p> </hgroup> </header> Loading examples/blog/federation/mod.ts +29 −3 Original line number Diff line number Diff line Loading @@ -12,6 +12,9 @@ import { import { getBlog } from "../models/blog.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"; // The `Federation<TContextData>` object is a registry that registers // federation-related callbacks: Loading Loading @@ -104,12 +107,29 @@ federation.setInboxListeners("/users/{handle}/inbox") .on(Follow, async (ctx, follow) => { const blog = await getBlog(); if (blog == null) return; if (follow.id == null) return; const actorUri = ctx.getActorUri(blog.handle); if (follow.objectId?.href != actorUri.href) { return; } const recipient = await follow.getActor(ctx); if (!isActor(recipient)) return; if ( !isActor(recipient) || recipient.id == null || recipient.preferredUsername == null ) return; const handle = `@${recipient.preferredUsername.toString()}@${recipient.id.host}`; await addFollower({ activityId: follow.id.href, id: recipient.id.href, name: recipient.name?.toString() ?? "", url: recipient.url == null ? recipient.id.href : recipient.url instanceof Link ? (recipient.url.href ?? recipient.id).href : recipient.url.href, handle, }); await ctx.sendActivity( { handle: blog.handle }, recipient, Loading @@ -119,7 +139,13 @@ federation.setInboxListeners("/users/{handle}/inbox") }), ); }) .on(Undo, (_ctx, undo) => { console.log({ undo }); .on(Undo, async (ctx, undo) => { const object = await undo.getObject(ctx); if (object instanceof Follow) { if (object.id == null) return; await removeFollower(object.id.href); } else { console.debug(undo); } }) .onError((e) => console.error(e)); examples/blog/fresh.gen.ts +2 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import * as $_app from "./routes/_app.tsx"; import * as $_middleware from "./routes/_middleware.ts"; import * as $followers from "./routes/followers.tsx"; import * as $index from "./routes/index.tsx"; import * as $posts_uuid_ from "./routes/posts/[uuid].tsx"; import * as $posts_index from "./routes/posts/index.tsx"; Loading @@ -15,6 +16,7 @@ const manifest = { routes: { "./routes/_app.tsx": $_app, "./routes/_middleware.ts": $_middleware, "./routes/followers.tsx": $followers, "./routes/index.tsx": $index, "./routes/posts/[uuid].tsx": $posts_uuid_, "./routes/posts/index.tsx": $posts_index, Loading examples/blog/models/follower.ts 0 → 100644 +58 −0 Original line number Diff line number Diff line import { openKv } from "./kv.ts"; export interface Follower { activityId: string; id: string; name: string; url: string; handle: string; } export async function addFollower(follower: Follower): Promise<Follower> { const kv = await openKv(); const followers = await kv.get<bigint>(["followers"]); await kv.atomic() .check(followers) .set(["follower", follower.activityId], follower) .set(["followers"], (followers.value ?? 0n) + 1n) .commit(); return follower; } export async function removeFollower(activityId: string): Promise<void> { const kv = await openKv(); const follower = await kv.get<Follower>(["follower", activityId]); const followers = await kv.get<bigint>(["followers"]); if ( follower == null || follower.value == null || followers == null || followers.value == null ) return; await kv.atomic() .check(follower) .check(followers) .delete(["follower", activityId]) .set(["followers"], followers.value - 1n) .commit(); } export async function getFollowers( limit = 5, cursor?: string, ): Promise<{ followers: Follower[]; nextCursor: string | null }> { const kv = await openKv(); const it = kv.list<Follower>({ prefix: ["follower"] }, { limit, cursor, }); const followers: Follower[] = []; for await (const entry of it) { followers.push(entry.value); } return { followers, nextCursor: followers.length < limit ? null : it.cursor }; } export async function countFollowers(): Promise<bigint> { const kv = await openKv(); const record = await kv.get(["followers"]); return (record?.value as bigint | null) ?? 0n; } examples/blog/models/post.ts +1 −2 Original line number Diff line number Diff line Loading @@ -18,10 +18,9 @@ export async function addPost(post: Omit<Post, "uuid"> | Post): Promise<Post> { uuid = post.uuid; } const newPost = { uuid, ...post, published: post.published.toString() }; const one = new Deno.KvU64(1n); await kv.atomic() .set(["post", uuid], newPost) .sum(["count"], one.value) .sum(["count"], 1n) .commit(); return { uuid, ...post }; } Loading Loading
examples/blog/components/PostList.tsx +7 −1 Original line number Diff line number Diff line Loading @@ -8,6 +8,7 @@ export interface PostListProps extends PostFormProps { blog: Blog; posts: PostModel[]; total: bigint; followers: bigint; nextCursor: string | null; domain: string; } Loading @@ -17,6 +18,7 @@ export default function PostList( blog, posts, total, followers, nextCursor, domain, error, Loading @@ -32,7 +34,11 @@ export default function PostList( <hgroup> <h1>{blog.title}</h1> <p> <strong>@{blog.handle}@{domain}</strong> · {blog.description} <strong>@{blog.handle}@{domain}</strong> ·{" "} <a href="/followers"> {followers === 1n ? "1 follower" : `${followers} followers`} </a>{" "} · {blog.description} </p> </hgroup> </header> Loading
examples/blog/federation/mod.ts +29 −3 Original line number Diff line number Diff line Loading @@ -12,6 +12,9 @@ import { import { getBlog } from "../models/blog.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"; // The `Federation<TContextData>` object is a registry that registers // federation-related callbacks: Loading Loading @@ -104,12 +107,29 @@ federation.setInboxListeners("/users/{handle}/inbox") .on(Follow, async (ctx, follow) => { const blog = await getBlog(); if (blog == null) return; if (follow.id == null) return; const actorUri = ctx.getActorUri(blog.handle); if (follow.objectId?.href != actorUri.href) { return; } const recipient = await follow.getActor(ctx); if (!isActor(recipient)) return; if ( !isActor(recipient) || recipient.id == null || recipient.preferredUsername == null ) return; const handle = `@${recipient.preferredUsername.toString()}@${recipient.id.host}`; await addFollower({ activityId: follow.id.href, id: recipient.id.href, name: recipient.name?.toString() ?? "", url: recipient.url == null ? recipient.id.href : recipient.url instanceof Link ? (recipient.url.href ?? recipient.id).href : recipient.url.href, handle, }); await ctx.sendActivity( { handle: blog.handle }, recipient, Loading @@ -119,7 +139,13 @@ federation.setInboxListeners("/users/{handle}/inbox") }), ); }) .on(Undo, (_ctx, undo) => { console.log({ undo }); .on(Undo, async (ctx, undo) => { const object = await undo.getObject(ctx); if (object instanceof Follow) { if (object.id == null) return; await removeFollower(object.id.href); } else { console.debug(undo); } }) .onError((e) => console.error(e));
examples/blog/fresh.gen.ts +2 −0 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import * as $_app from "./routes/_app.tsx"; import * as $_middleware from "./routes/_middleware.ts"; import * as $followers from "./routes/followers.tsx"; import * as $index from "./routes/index.tsx"; import * as $posts_uuid_ from "./routes/posts/[uuid].tsx"; import * as $posts_index from "./routes/posts/index.tsx"; Loading @@ -15,6 +16,7 @@ const manifest = { routes: { "./routes/_app.tsx": $_app, "./routes/_middleware.ts": $_middleware, "./routes/followers.tsx": $followers, "./routes/index.tsx": $index, "./routes/posts/[uuid].tsx": $posts_uuid_, "./routes/posts/index.tsx": $posts_index, Loading
examples/blog/models/follower.ts 0 → 100644 +58 −0 Original line number Diff line number Diff line import { openKv } from "./kv.ts"; export interface Follower { activityId: string; id: string; name: string; url: string; handle: string; } export async function addFollower(follower: Follower): Promise<Follower> { const kv = await openKv(); const followers = await kv.get<bigint>(["followers"]); await kv.atomic() .check(followers) .set(["follower", follower.activityId], follower) .set(["followers"], (followers.value ?? 0n) + 1n) .commit(); return follower; } export async function removeFollower(activityId: string): Promise<void> { const kv = await openKv(); const follower = await kv.get<Follower>(["follower", activityId]); const followers = await kv.get<bigint>(["followers"]); if ( follower == null || follower.value == null || followers == null || followers.value == null ) return; await kv.atomic() .check(follower) .check(followers) .delete(["follower", activityId]) .set(["followers"], followers.value - 1n) .commit(); } export async function getFollowers( limit = 5, cursor?: string, ): Promise<{ followers: Follower[]; nextCursor: string | null }> { const kv = await openKv(); const it = kv.list<Follower>({ prefix: ["follower"] }, { limit, cursor, }); const followers: Follower[] = []; for await (const entry of it) { followers.push(entry.value); } return { followers, nextCursor: followers.length < limit ? null : it.cursor }; } export async function countFollowers(): Promise<bigint> { const kv = await openKv(); const record = await kv.get(["followers"]); return (record?.value as bigint | null) ?? 0n; }
examples/blog/models/post.ts +1 −2 Original line number Diff line number Diff line Loading @@ -18,10 +18,9 @@ export async function addPost(post: Omit<Post, "uuid"> | Post): Promise<Post> { uuid = post.uuid; } const newPost = { uuid, ...post, published: post.published.toString() }; const one = new Deno.KvU64(1n); await kv.atomic() .set(["post", uuid], newPost) .sum(["count"], one.value) .sum(["count"], 1n) .commit(); return { uuid, ...post }; } Loading