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 atb-52-css-token-extraction 102 lines 3.2 kB view raw
1import { Hono } from "hono"; 2import type { AppContext } from "../lib/app-context.js"; 3import { categories, boards } from "@atbb/db"; 4import { eq, asc } from "drizzle-orm"; 5import { serializeCategory, serializeBoard, parseBigIntParam } from "./helpers.js"; 6import { handleRouteError } from "../lib/route-errors.js"; 7 8/** 9 * Factory function that creates category routes with access to app context. 10 * 11 * Note: GET /api/categories/:id/topics endpoint removed. 12 * Reason: posts table has no categoryUri/categoryId field to filter by. 13 * posts.forumUri stores forum URIs, not category URIs. 14 * This endpoint would always return empty arrays. 15 * TODO: Add categoryUri field to posts schema + update indexer (ATB-12 or later) 16 */ 17export function createCategoriesRoutes(ctx: AppContext) { 18 return new Hono() 19 .get("/", async (c) => { 20 try { 21 const allCategories = await ctx.db 22 .select() 23 .from(categories) 24 .orderBy(categories.sortOrder) 25 .limit(1000); // Defensive limit 26 27 return c.json({ 28 categories: allCategories.map(serializeCategory), 29 }); 30 } catch (error) { 31 return handleRouteError(c, error, "Failed to retrieve categories", { 32 operation: "GET /api/categories", 33 logger: ctx.logger, 34 }); 35 } 36 }) 37 .get("/:id", async (c) => { 38 const raw = c.req.param("id"); 39 const id = parseBigIntParam(raw); 40 if (id === null) { 41 return c.json({ error: "Invalid category ID" }, 400); 42 } 43 44 try { 45 const [category] = await ctx.db 46 .select() 47 .from(categories) 48 .where(eq(categories.id, id)) 49 .limit(1); 50 51 if (!category) { 52 return c.json({ error: "Category not found" }, 404); 53 } 54 55 return c.json(serializeCategory(category)); 56 } catch (error) { 57 return handleRouteError(c, error, "Failed to retrieve category", { 58 operation: "GET /api/categories/:id", 59 logger: ctx.logger, 60 categoryId: raw, 61 }); 62 } 63 }) 64 .get("/:id/boards", async (c) => { 65 const { id } = c.req.param(); 66 67 const categoryId = parseBigIntParam(id); 68 if (categoryId === null) { 69 return c.json({ error: "Invalid category ID format" }, 400); 70 } 71 72 try { 73 // Check if category exists 74 const [category] = await ctx.db 75 .select() 76 .from(categories) 77 .where(eq(categories.id, categoryId)) 78 .limit(1); 79 80 if (!category) { 81 return c.json({ error: "Category not found" }, 404); 82 } 83 84 const categoryBoards = await ctx.db 85 .select() 86 .from(boards) 87 .where(eq(boards.categoryId, categoryId)) 88 .orderBy(asc(boards.sortOrder)) 89 .limit(1000); // Defensive limit 90 91 return c.json({ 92 boards: categoryBoards.map(serializeBoard), 93 }); 94 } catch (error) { 95 return handleRouteError(c, error, "Failed to retrieve boards", { 96 operation: "GET /api/categories/:id/boards", 97 logger: ctx.logger, 98 categoryId: id, 99 }); 100 } 101 }); 102}