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 155 lines 4.4 kB view raw
1import { defineCommand } from "citty"; 2import consola from "consola"; 3import { input } from "@inquirer/prompts"; 4import postgres from "postgres"; 5import { drizzle } from "drizzle-orm/postgres-js"; 6import * as schema from "@atbb/db"; 7import { ForumAgent } from "@atbb/atproto"; 8import { loadCliConfig } from "../lib/config.js"; 9import { checkEnvironment } from "../lib/preflight.js"; 10import { createCategory } from "../lib/steps/create-category.js"; 11import { isProgrammingError } from "../lib/errors.js"; 12import { logger } from "../lib/logger.js"; 13 14const categoryAddCommand = defineCommand({ 15 meta: { 16 name: "add", 17 description: "Add a new category to the forum", 18 }, 19 args: { 20 name: { 21 type: "string", 22 description: "Category name", 23 }, 24 description: { 25 type: "string", 26 description: "Category description (optional)", 27 }, 28 slug: { 29 type: "string", 30 description: "URL-friendly identifier (auto-derived from name if omitted)", 31 }, 32 "sort-order": { 33 type: "string", 34 description: "Numeric sort position — lower values appear first", 35 }, 36 }, 37 async run({ args }) { 38 consola.box("atBB — Add Category"); 39 40 const config = loadCliConfig(); 41 const envCheck = checkEnvironment(config); 42 43 if (!envCheck.ok) { 44 consola.error("Missing required environment variables:"); 45 for (const name of envCheck.errors) { 46 consola.error(` - ${name}`); 47 } 48 consola.info("Set these in your .env file or environment, then re-run."); 49 process.exit(1); 50 } 51 52 const sql = postgres(config.databaseUrl); 53 const db = drizzle(sql, { schema }); 54 55 async function cleanup() { 56 await sql.end(); 57 } 58 59 try { 60 await sql`SELECT 1`; 61 consola.success("Database connection successful"); 62 } catch (error) { 63 consola.error( 64 "Failed to connect to database:", 65 error instanceof Error ? error.message : String(error) 66 ); 67 await cleanup(); 68 process.exit(1); 69 } 70 71 consola.start("Authenticating as Forum DID..."); 72 const forumAgent = new ForumAgent( 73 config.pdsUrl, 74 config.forumHandle, 75 config.forumPassword, 76 logger 77 ); 78 try { 79 await forumAgent.initialize(); 80 } catch (error) { 81 consola.error( 82 "Failed to reach PDS during authentication:", 83 error instanceof Error ? error.message : String(error) 84 ); 85 try { await forumAgent.shutdown(); } catch {} 86 await cleanup(); 87 process.exit(1); 88 } 89 90 if (!forumAgent.isAuthenticated()) { 91 const status = forumAgent.getStatus(); 92 consola.error(`Failed to authenticate: ${status.error}`); 93 await forumAgent.shutdown(); 94 await cleanup(); 95 process.exit(1); 96 } 97 98 const agent = forumAgent.getAgent()!; 99 consola.success(`Authenticated as ${config.forumHandle}`); 100 101 const name = 102 args.name ?? 103 (await input({ message: "Category name:", default: "General" })); 104 105 const description = 106 args.description ?? 107 (await input({ message: "Category description (optional):" })); 108 109 const sortOrderRaw = args["sort-order"]; 110 const sortOrder = 111 sortOrderRaw !== undefined ? parseInt(sortOrderRaw, 10) : undefined; 112 113 try { 114 const result = await createCategory(db, agent, config.forumDid, { 115 name, 116 ...(description && { description }), 117 ...(args.slug && { slug: args.slug }), 118 ...(sortOrder !== undefined && !isNaN(sortOrder) && { sortOrder }), 119 }); 120 121 if (result.skipped) { 122 consola.warn(`Category "${result.existingName}" already exists: ${result.uri}`); 123 } else { 124 consola.success(`Created category "${name}"`); 125 consola.info(`URI: ${result.uri}`); 126 } 127 } catch (error) { 128 if (isProgrammingError(error)) throw error; 129 consola.error( 130 "Failed to create category:", 131 JSON.stringify({ 132 name, 133 forumDid: config.forumDid, 134 error: error instanceof Error ? error.message : String(error), 135 }) 136 ); 137 await forumAgent.shutdown(); 138 await cleanup(); 139 process.exit(1); 140 } 141 142 await forumAgent.shutdown(); 143 await cleanup(); 144 }, 145}); 146 147export const categoryCommand = defineCommand({ 148 meta: { 149 name: "category", 150 description: "Manage forum categories", 151 }, 152 subCommands: { 153 add: categoryAddCommand, 154 }, 155});