import type { AuthService, Feature, RouteHandler, ServerContext } from "@stc/contracts"; import { createAuth } from "./auth"; import { runMigrations } from "./migrate"; import { json } from "./http"; import { adminRoutes } from "./admin"; import { libraryRoutes } from "./catalog"; import { lessonRoutes } from "./lesson"; import { userDataRoutes } from "./userdata"; /** * The learner feature: auth, library, reader, notes, progress and exercises. * Returns the routes plus the `AuthService` that replaces the server's anonymous one. */ export function learnerFeature(ctx: ServerContext): Feature & { auth: AuthService } { const ready = runMigrations(ctx.db); ready.catch((e) => console.error("[learner] migrations failed", e)); const auth = createAuth(ctx, ready); const routes: Feature["routes"] = { "/api/auth/*": { GET: (req, rc) => auth.handler(req, rc.user), POST: (req, rc) => auth.handler(req, rc.user), }, "/api/me": { GET: async (_req, rc) => json({ user: rc.user }) }, "/api/learner/providers": { GET: async () => json({ emailPassword: true, providers: auth.providers(), sso: await auth.hasSso() }), }, ...adminRoutes(ctx, auth.service.invalidate), ...libraryRoutes(ctx), ...lessonRoutes(ctx), ...userDataRoutes(ctx), }; // Every handler waits for the learner migrations (a no-op once Phase 2 runs them in migrate()). for (const methods of Object.values(routes)) { for (const [m, h] of Object.entries(methods)) { (methods as Record)[m] = async (req, rc) => { await ready; return h!(req, rc); }; } } return { name: "learner", routes, auth: auth.service }; }