WIP! A BB-style forum, on the ATmosphere!
We're still working... we'll be back soon when we have something to show off!
node
typescript
hono
htmx
atproto
1import type { AtpAgent } from "@atproto/api";
2import type { Database } from "@atbb/db";
3import { forums } from "@atbb/db";
4import { isProgrammingError } from "@atbb/atproto";
5
6interface CreateForumInput {
7 name: string;
8 description?: string;
9}
10
11interface CreateForumResult {
12 created: boolean;
13 skipped: boolean;
14 uri?: string;
15 cid?: string;
16 existingName?: string;
17}
18
19/**
20 * Create the space.atbb.forum.forum/self record on the Forum DID's PDS
21 * and insert it into the database.
22 * Idempotent: skips if the record already exists on the PDS.
23 */
24export async function createForumRecord(
25 db: Database,
26 agent: AtpAgent,
27 forumDid: string,
28 input: CreateForumInput
29): Promise<CreateForumResult> {
30 // Check if forum record already exists
31 try {
32 const existing = await agent.com.atproto.repo.getRecord({
33 repo: forumDid,
34 collection: "space.atbb.forum.forum",
35 rkey: "self",
36 });
37
38 return {
39 created: false,
40 skipped: true,
41 uri: existing.data.uri,
42 cid: existing.data.cid,
43 existingName: (existing.data.value as any)?.name,
44 };
45 } catch (error) {
46 if (isProgrammingError(error)) throw error;
47
48 // Only proceed if the record was not found.
49 // XRPC "RecordNotFound" errors have an `error` property on the thrown object.
50 const isNotFound =
51 error instanceof Error &&
52 "status" in error &&
53 (error as any).error === "RecordNotFound";
54
55 if (!isNotFound) {
56 throw error;
57 }
58 }
59
60 const response = await agent.com.atproto.repo.createRecord({
61 repo: forumDid,
62 collection: "space.atbb.forum.forum",
63 rkey: "self",
64 record: {
65 $type: "space.atbb.forum.forum",
66 name: input.name,
67 ...(input.description && { description: input.description }),
68 createdAt: new Date().toISOString(),
69 },
70 });
71
72 // Insert forum record into DB so downstream steps can reference it
73 await db.insert(forums).values({
74 did: forumDid,
75 rkey: "self",
76 cid: response.data.cid,
77 name: input.name,
78 description: input.description ?? null,
79 indexedAt: new Date(),
80 });
81
82 return {
83 created: true,
84 skipped: false,
85 uri: response.data.uri,
86 cid: response.data.cid,
87 };
88}