import type { Context } from "hono"; import { Hono } from "hono"; import { z } from "zod"; import { and, eq } from "../../db/drizzle.ts"; import { getDb } from "../../db/index.ts"; import { sphereModules, type spheres } from "../../db/schema/index.ts"; import { requireAuth, type AuthEnv } from "../../auth/index.ts"; import { putPdsRecord } from "../../pds.ts"; import { enableModuleSchema } from "../schemas.ts"; import { findSphere, getEnabledModules, formatModules } from "./helpers.ts"; const SPHERE_COLLECTION = "site.exosphere.sphere.profile" as const; type Sphere = typeof spheres.$inferSelect; /** Sync the sphere declaration (including modules) to the owner's PDS. */ async function syncSpherePds(c: Context, sphere: Sphere) { const modules = getEnabledModules(sphere.id).map((m) => m.moduleName); await putPdsRecord(c.var.session, SPHERE_COLLECTION, "self", { name: sphere.name, description: sphere.description ?? undefined, visibility: sphere.visibility, modules, createdAt: sphere.createdAt, }); } export function createModuleRoutes(availableModules: string[]) { const app = new Hono(); // List enabled modules for a sphere app.get("/:handle/modules", (c) => { const sphere = findSphere(c.req.param("handle")); if (!sphere) { return c.json({ error: "Sphere not found" }, 404); } return c.json({ modules: formatModules(getEnabledModules(sphere.id)), available: availableModules, }); }); // Enable module app.post("/:handle/modules", requireAuth, async (c) => { const sphere = findSphere(c.req.param("handle")); if (!sphere) { return c.json({ error: "Sphere not found" }, 404); } // Only the owner can manage modules — changes are synced to PDS which requires the owner's session if (c.var.did !== sphere.ownerDid) { return c.json({ error: "Forbidden" }, 403); } const body = await c.req.json(); const parsed = enableModuleSchema.safeParse(body); if (!parsed.success) { return c.json({ error: z.flattenError(parsed.error) }, 400); } const moduleName = parsed.data.module; if (!availableModules.includes(moduleName)) { return c.json( { error: `Unknown module: ${moduleName}. Available: ${availableModules.join(", ")}`, }, 400, ); } getDb() .insert(sphereModules) .values({ sphereId: sphere.id, moduleName }) .onConflictDoNothing() .run(); await syncSpherePds(c, sphere); return c.json({ modules: formatModules(getEnabledModules(sphere.id)), }); }); // Disable module app.delete("/:handle/modules/:moduleName", requireAuth, async (c) => { const sphere = findSphere(c.req.param("handle")); if (!sphere) { return c.json({ error: "Sphere not found" }, 404); } if (c.var.did !== sphere.ownerDid) { return c.json({ error: "Forbidden" }, 403); } getDb() .delete(sphereModules) .where( and( eq(sphereModules.sphereId, sphere.id), eq(sphereModules.moduleName, c.req.param("moduleName")), ), ) .run(); await syncSpherePds(c, sphere); return c.json({ modules: formatModules(getEnabledModules(sphere.id)), }); }); return app; }