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 142 lines 4.2 kB view raw
1import type { AppContext } from "./app-context.js"; 2import { roles } from "@atbb/db"; 3import { and, eq } from "drizzle-orm"; 4 5interface DefaultRole { 6 name: string; 7 description: string; 8 permissions: string[]; 9 priority: number; 10 critical: boolean; 11} 12 13const DEFAULT_ROLES: DefaultRole[] = [ 14 { 15 name: "Owner", 16 description: "Forum owner with full control", 17 permissions: ["*"], 18 priority: 0, 19 critical: true, 20 }, 21 { 22 name: "Admin", 23 description: "Can manage forum structure and users", 24 permissions: [ 25 "space.atbb.permission.manageCategories", 26 "space.atbb.permission.manageRoles", 27 "space.atbb.permission.manageMembers", 28 "space.atbb.permission.manageThemes", 29 "space.atbb.permission.moderatePosts", 30 "space.atbb.permission.banUsers", 31 "space.atbb.permission.pinTopics", 32 "space.atbb.permission.lockTopics", 33 "space.atbb.permission.createTopics", 34 "space.atbb.permission.createPosts", 35 ], 36 priority: 10, 37 critical: true, 38 }, 39 { 40 name: "Moderator", 41 description: "Can moderate content and users", 42 permissions: [ 43 "space.atbb.permission.moderatePosts", 44 "space.atbb.permission.banUsers", 45 "space.atbb.permission.pinTopics", 46 "space.atbb.permission.lockTopics", 47 "space.atbb.permission.createTopics", 48 "space.atbb.permission.createPosts", 49 ], 50 priority: 20, 51 critical: true, 52 }, 53 { 54 name: "Member", 55 description: "Regular forum member", 56 permissions: [ 57 "space.atbb.permission.createTopics", 58 "space.atbb.permission.createPosts", 59 ], 60 priority: 30, 61 critical: true, 62 }, 63]; 64 65/** 66 * Seed default roles to Forum DID's PDS. 67 * 68 * Idempotent: Checks for existing roles by name before creating. 69 * Safe to run on every startup. 70 * 71 * @throws Error if ForumAgent is unavailable or if any critical role fails to seed 72 */ 73export async function seedDefaultRoles(ctx: AppContext): Promise<{ created: number; skipped: number }> { 74 // Check ForumAgent availability 75 if (!ctx.forumAgent) { 76 throw new Error("ForumAgent not available - permission system cannot function without roles. Check FORUM_HANDLE and FORUM_PASSWORD environment variables."); 77 } 78 79 const agent = ctx.forumAgent.getAgent(); 80 if (!agent) { 81 throw new Error("ForumAgent not authenticated - permission system cannot function without roles. Check FORUM_HANDLE and FORUM_PASSWORD are valid."); 82 } 83 84 let created = 0; 85 let skipped = 0; 86 87 for (const defaultRole of DEFAULT_ROLES) { 88 try { 89 // Check if role already exists by name 90 const [existingRole] = await ctx.db 91 .select() 92 .from(roles) 93 .where(and(eq(roles.did, ctx.config.forumDid), eq(roles.name, defaultRole.name))) 94 .limit(1); 95 96 if (existingRole) { 97 ctx.logger.info(`Role "${defaultRole.name}" already exists, skipping`, { 98 operation: "seedDefaultRoles", 99 roleName: defaultRole.name, 100 }); 101 skipped++; 102 continue; 103 } 104 105 // Create role record on Forum DID's PDS 106 const response = await agent.com.atproto.repo.createRecord({ 107 repo: ctx.config.forumDid, 108 collection: "space.atbb.forum.role", 109 record: { 110 $type: "space.atbb.forum.role", 111 name: defaultRole.name, 112 description: defaultRole.description, 113 permissions: defaultRole.permissions, 114 priority: defaultRole.priority, 115 createdAt: new Date().toISOString(), 116 }, 117 }); 118 119 ctx.logger.info(`Created default role "${defaultRole.name}"`, { 120 operation: "seedDefaultRoles", 121 roleName: defaultRole.name, 122 uri: response.data.uri, 123 cid: response.data.cid, 124 }); 125 126 created++; 127 } catch (error) { 128 ctx.logger.error(`Failed to seed role "${defaultRole.name}"`, { 129 operation: "seedDefaultRoles", 130 roleName: defaultRole.name, 131 error: error instanceof Error ? error.message : String(error), 132 }); 133 if (defaultRole.critical) { 134 throw new Error( 135 `Failed to seed critical role "${defaultRole.name}": ${error instanceof Error ? error.message : String(error)}` 136 ); 137 } 138 } 139 } 140 141 return { created, skipped }; 142}