export const prerender = false; import type { APIRoute } from "astro"; import { eq } from "drizzle-orm"; import { createDb } from "@/db"; import { profiles } from "@/db/schema"; function generateApiKey(): string { const bytes = new Uint8Array(24); crypto.getRandomValues(bytes); return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join(""); } async function hashKey(key: string): Promise { const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key)); return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join(""); } export const POST: APIRoute = async ({ request, locals }) => { const runtime = locals.runtime as { env: { DATABASE_URL: string } }; const db = createDb(runtime.env.DATABASE_URL); const ct = request.headers.get("Content-Type")?.split(";")[0].trim(); if (ct !== "application/json") { return Response.json({ error: "Content-Type must be application/json" }, { status: 415 }); } let body: { username?: string; name?: string }; try { body = await request.json(); } catch { return Response.json({ error: "Invalid JSON body" }, { status: 400 }); } const { username, name } = body; if (!username || typeof username !== "string" || username.length < 3 || username.length > 30 || !/^[a-z0-9-]+$/.test(username)) { return Response.json({ error: "username must be 3-30 characters, lowercase letters, numbers, and hyphens only" }, { status: 400 }); } if (!name || typeof name !== "string" || name.length < 1 || name.length > 100) { return Response.json({ error: "name must be 1-100 characters" }, { status: 400 }); } const [existing] = await db.select({ id: profiles.id }).from(profiles).where(eq(profiles.username, username)); if (existing) { return Response.json({ error: `Username "${username}" is taken` }, { status: 409 }); } const apiKey = generateApiKey(); const apiKeyHash = await hashKey(apiKey); const [profile] = await db.insert(profiles).values({ username, name, apiKeyHash, apiKeyPrefix: apiKey.slice(0, 8), }).returning({ id: profiles.id, username: profiles.username, name: profiles.name, balance: profiles.balance }); return Response.json({ apiKey, username: profile.username, name: profile.name, balance: profile.balance }, { status: 201 }); };