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

feat(appview): add GET /api/admin/themes — unfiltered theme list for admin UI (ATB-58)

+97 -1
+56
apps/appview/src/routes/__tests__/admin.test.ts
··· 2445 2445 }); 2446 2446 }); 2447 2447 2448 + describe("GET /api/admin/themes", () => { 2449 + it("returns empty array when no themes exist", async () => { 2450 + const res = await app.request("/api/admin/themes"); 2451 + expect(res.status).toBe(200); 2452 + const body = await res.json(); 2453 + expect(body).toHaveProperty("themes"); 2454 + expect(body.themes).toEqual([]); 2455 + }); 2456 + 2457 + it("returns all themes regardless of policy availability", async () => { 2458 + // Insert two themes but only add one to policy 2459 + await ctx.db.insert(themes).values([ 2460 + { 2461 + did: ctx.config.forumDid, 2462 + rkey: "3lbltheme1aa", 2463 + cid: "bafytheme1", 2464 + name: "Neobrutal Light", 2465 + colorScheme: "light", 2466 + tokens: { "color-bg": "#f5f0e8" }, 2467 + createdAt: new Date(), 2468 + indexedAt: new Date(), 2469 + }, 2470 + { 2471 + did: ctx.config.forumDid, 2472 + rkey: "3lbltheme2bb", 2473 + cid: "bafytheme2", 2474 + name: "Neobrutal Dark", 2475 + colorScheme: "dark", 2476 + tokens: { "color-bg": "#1a1a1a" }, 2477 + createdAt: new Date(), 2478 + indexedAt: new Date(), 2479 + }, 2480 + ]); 2481 + 2482 + const res = await app.request("/api/admin/themes"); 2483 + expect(res.status).toBe(200); 2484 + const body = await res.json(); 2485 + 2486 + // Returns BOTH themes — not filtered by policy 2487 + expect(body.themes).toHaveLength(2); 2488 + expect(body.themes[0]).toMatchObject({ 2489 + name: "Neobrutal Light", 2490 + colorScheme: "light", 2491 + }); 2492 + expect(body.themes[0]).toHaveProperty("tokens"); 2493 + expect(body.themes[0]).toHaveProperty("uri"); 2494 + expect(body.themes[0].uri).toContain("space.atbb.forum.theme"); 2495 + }); 2496 + 2497 + it("returns 401 when not authenticated", async () => { 2498 + mockUser = null; 2499 + const res = await app.request("/api/admin/themes"); 2500 + expect(res.status).toBe(401); 2501 + }); 2502 + }); 2503 + 2448 2504 describe("POST /api/admin/themes", () => { 2449 2505 it("creates theme and returns 201 with uri and cid", async () => { 2450 2506 const res = await app.request("/api/admin/themes", {
+41 -1
apps/appview/src/routes/admin.ts
··· 15 15 getForumAgentOrError, 16 16 } from "../lib/route-errors.js"; 17 17 import { TID } from "@atproto/common-web"; 18 - import { parseBigIntParam } from "./helpers.js"; 18 + import { parseBigIntParam, serializeBigInt, serializeDate } from "./helpers.js"; 19 19 20 20 export function createAdminRoutes(ctx: AppContext) { 21 21 const app = new Hono<{ Variables: Variables }>(); ··· 976 976 operation: "DELETE /api/admin/boards/:id", 977 977 logger: ctx.logger, 978 978 id: idParam, 979 + }); 980 + } 981 + } 982 + ); 983 + 984 + /** 985 + * GET /api/admin/themes 986 + * 987 + * Returns all themes for this forum — no policy filtering. 988 + * Admins need to see all themes, including drafts not yet in the policy. 989 + */ 990 + app.get( 991 + "/themes", 992 + requireAuth(ctx), 993 + requirePermission(ctx, "space.atbb.permission.manageThemes"), 994 + async (c) => { 995 + try { 996 + const themeList = await ctx.db 997 + .select() 998 + .from(themes) 999 + .where(eq(themes.did, ctx.config.forumDid)) 1000 + .limit(100); 1001 + 1002 + return c.json({ 1003 + themes: themeList.map((theme) => ({ 1004 + id: serializeBigInt(theme.id), 1005 + uri: `at://${theme.did}/space.atbb.forum.theme/${theme.rkey}`, 1006 + name: theme.name, 1007 + colorScheme: theme.colorScheme, 1008 + tokens: theme.tokens, 1009 + cssOverrides: theme.cssOverrides ?? null, 1010 + fontUrls: (theme.fontUrls as string[] | null) ?? null, 1011 + createdAt: serializeDate(theme.createdAt), 1012 + indexedAt: serializeDate(theme.indexedAt), 1013 + })), 1014 + }); 1015 + } catch (error) { 1016 + return handleRouteError(c, error, "Failed to retrieve themes", { 1017 + operation: "GET /api/admin/themes", 1018 + logger: ctx.logger, 979 1019 }); 980 1020 } 981 1021 }