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
at main 101 lines 2.6 kB view raw
1import type { AtpAgent } from "@atproto/api"; 2import type { Database } from "@atbb/db"; 3import { categories, forums } from "@atbb/db"; 4import { eq, and } from "drizzle-orm"; 5import { deriveSlug } from "../slug.js"; 6 7interface CreateCategoryInput { 8 name: string; 9 description?: string; 10 slug?: string; 11 sortOrder?: number; 12} 13 14interface CreateCategoryResult { 15 created: boolean; 16 skipped: boolean; 17 uri?: string; 18 cid?: string; 19 existingName?: string; 20} 21 22/** 23 * Create a space.atbb.forum.category record on the Forum DID's PDS 24 * and insert it into the database. 25 * Idempotent: skips if a category with the same name already exists. 26 */ 27export async function createCategory( 28 db: Database, 29 agent: AtpAgent, 30 forumDid: string, 31 input: CreateCategoryInput 32): Promise<CreateCategoryResult> { 33 // Check if category with this name already exists 34 const [existing] = await db 35 .select() 36 .from(categories) 37 .where(and(eq(categories.did, forumDid), eq(categories.name, input.name))) 38 .limit(1); 39 40 if (existing) { 41 return { 42 created: false, 43 skipped: true, 44 uri: `at://${existing.did}/space.atbb.forum.category/${existing.rkey}`, 45 cid: existing.cid, 46 existingName: existing.name, 47 }; 48 } 49 50 // Look up forum row for FK reference (optional — null if forum not yet in DB) 51 const [forum] = await db 52 .select() 53 .from(forums) 54 .where(and(eq(forums.did, forumDid), eq(forums.rkey, "self"))) 55 .limit(1); 56 57 if (!forum?.id) { 58 console.warn( 59 "Forum record not found in DB — inserting category with forumId: null", 60 JSON.stringify({ forumDid }) 61 ); 62 } 63 64 const slug = input.slug ?? deriveSlug(input.name); 65 const now = new Date(); 66 67 const response = await agent.com.atproto.repo.createRecord({ 68 repo: forumDid, 69 collection: "space.atbb.forum.category", 70 record: { 71 $type: "space.atbb.forum.category", 72 name: input.name, 73 ...(input.description && { description: input.description }), 74 slug, 75 ...(input.sortOrder !== undefined && { sortOrder: input.sortOrder }), 76 createdAt: now.toISOString(), 77 }, 78 }); 79 80 const rkey = response.data.uri.split("/").pop()!; 81 82 await db.insert(categories).values({ 83 did: forumDid, 84 rkey, 85 cid: response.data.cid, 86 name: input.name, 87 description: input.description ?? null, 88 slug, 89 sortOrder: input.sortOrder ?? null, 90 forumId: forum?.id ?? null, 91 createdAt: now, 92 indexedAt: now, 93 }); 94 95 return { 96 created: true, 97 skipped: false, 98 uri: response.data.uri, 99 cid: response.data.cid, 100 }; 101}