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 root/atb-56-theme-caching-layer 81 lines 2.4 kB view raw
1import type { Database } from "@atbb/db"; 2import { users, memberships } from "@atbb/db"; 3import { eq, and } from "drizzle-orm"; 4import type { SeededRole } from "./seed-roles.js"; 5 6interface AssignOwnerResult { 7 assigned: boolean; 8 skipped: boolean; 9 roleUri?: string; 10} 11 12/** 13 * Assign the Owner role to a user via direct DB insert. 14 * 15 * The CLI cannot write to the user's PDS (no user credentials), so 16 * this inserts the membership directly into the database as a bootstrap 17 * shortcut. When the user logs in via OAuth, the normal membership 18 * flow will create the PDS record on their repo. 19 * 20 * Idempotent: skips if the user already has a membership with the 21 * Owner role URI. 22 */ 23export async function assignOwnerRole( 24 db: Database, 25 forumDid: string, 26 ownerDid: string, 27 ownerHandle: string | undefined, 28 seededRoles: SeededRole[] 29): Promise<AssignOwnerResult> { 30 // Find the Owner role from seeded roles 31 const ownerRole = seededRoles.find((r) => r.name === "Owner"); 32 33 if (!ownerRole) { 34 throw new Error( 35 "Owner role not found in seeded roles. Run role seeding first." 36 ); 37 } 38 39 const forumUri = `at://${forumDid}/space.atbb.forum.forum/self`; 40 41 // Check if user already has a membership with this role 42 const [existingMembership] = await db 43 .select() 44 .from(memberships) 45 .where(and(eq(memberships.did, ownerDid), eq(memberships.roleUri, ownerRole.uri))) 46 .limit(1); 47 48 if (existingMembership) { 49 return { assigned: false, skipped: true, roleUri: ownerRole.uri }; 50 } 51 52 // Ensure user exists in the users table (FK constraint) 53 await db 54 .insert(users) 55 .values({ 56 did: ownerDid, 57 handle: ownerHandle ?? null, 58 indexedAt: new Date(), 59 }) 60 .onConflictDoNothing(); 61 62 // Insert membership directly into DB. 63 // rkey and cid use "bootstrap" sentinel — there is no PDS record backing 64 // this membership yet. When the user first logs in via OAuth, 65 // createMembershipForUser detects cid==="bootstrap" and upgrades the row 66 // by writing a real PDS record and updating rkey/cid. 67 const now = new Date(); 68 await db.insert(memberships).values({ 69 did: ownerDid, 70 rkey: "bootstrap", 71 cid: "bootstrap", 72 forumUri, 73 roleUri: ownerRole.uri, 74 role: "Owner", 75 joinedAt: now, 76 createdAt: now, 77 indexedAt: now, 78 }); 79 80 return { assigned: true, skipped: false, roleUri: ownerRole.uri }; 81}