import { Elysia } from "elysia"; import { cors } from "@elysiajs/cors"; import { taskRoutes } from "./routes/tasks"; import { adminRoutes } from "./routes/admin"; import { projectRoutes } from "./routes/projects"; import { chatRoutes } from "./routes/chat"; import { auth } from "./lib/auth"; import { db } from "./db"; import { tasks, users } from "./db/schema"; import { isNull, asc, sql, eq } from "drizzle-orm"; const PORT = process.env.PORT || 3100; // Backfill task numbers for existing tasks that don't have one async function backfillTaskNumbers() { const unnumbered = await db .select() .from(tasks) .where(isNull(tasks.taskNumber)) .orderBy(asc(tasks.createdAt)); if (unnumbered.length === 0) return; const maxNum = await db .select({ max: sql`COALESCE(MAX(${tasks.taskNumber}), 0)` }) .from(tasks); let next = (maxNum[0]?.max ?? 0) + 1; for (const task of unnumbered) { await db .update(tasks) .set({ taskNumber: next++ }) .where(sql`${tasks.id} = ${task.id}`); } console.log(`Backfilled ${unnumbered.length} task numbers (${next - unnumbered.length} to ${next - 1})`); } backfillTaskNumbers().catch(console.error); // Ensure donovan@donovankelly.xyz is admin async function ensureAdmin() { const adminEmail = "donovan@donovankelly.xyz"; const result = await db .update(users) .set({ role: "admin" }) .where(eq(users.email, adminEmail)) .returning({ id: users.id, email: users.email, role: users.role }); if (result.length) { console.log(`Admin role ensured for ${adminEmail}`); } } ensureAdmin().catch(console.error); const app = new Elysia() .use( cors({ origin: ["https://dash.donovankelly.xyz", "https://queue.donovankelly.xyz", "http://localhost:5173"], credentials: true, allowedHeaders: ["Content-Type", "Authorization", "Cookie"], methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], }) ) // Mount BetterAuth handler .all("/api/auth/*", async ({ request }) => { return auth.handler(request); }) // Invite route - create a user (bearer token or session auth) .post("/api/invite", async ({ request, headers, body }) => { const bearerToken = process.env.API_BEARER_TOKEN || "hammer-dev-token"; const authHeader = headers["authorization"]; // Check bearer token first let authorized = authHeader === `Bearer ${bearerToken}`; // If no bearer token, check session if (!authorized) { const session = await auth.api.getSession({ headers: request.headers }); authorized = !!session; } if (!authorized) { return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" }, }); } const { email, password, name } = body as { email: string; password: string; name: string; }; if (!email || !password || !name) { return new Response( JSON.stringify({ error: "email, password, and name are required" }), { status: 400, headers: { "Content-Type": "application/json" } } ); } try { const user = await auth.api.signUpEmail({ body: { email, password, name }, }); return new Response(JSON.stringify({ success: true, user }), { headers: { "Content-Type": "application/json" }, }); } catch (e: any) { return new Response( JSON.stringify({ error: e.message || "Failed to create user" }), { status: 400, headers: { "Content-Type": "application/json" } } ); } }) .use(taskRoutes) .use(projectRoutes) .use(adminRoutes) .use(chatRoutes) // Current user info (role, etc.) .get("/api/me", async ({ request }) => { try { const session = await auth.api.getSession({ headers: request.headers }); if (!session?.user) return { authenticated: false }; return { authenticated: true, user: { id: session.user.id, name: session.user.name, email: session.user.email, role: (session.user as any).role || "user", }, }; } catch { return { authenticated: false }; } }) .get("/health", () => ({ status: "ok", service: "hammer-queue" })) .onError(({ error, set }) => { const msg = (error as any)?.message || String(error); if (msg === "Unauthorized") { set.status = 401; return { error: "Unauthorized" }; } if (msg === "Task not found") { set.status = 404; return { error: "Task not found" }; } console.error("Unhandled error:", msg); set.status = 500; return { error: "Internal server error" }; }) .listen(PORT); console.log(`🔨 Hammer Queue API running on port ${PORT}`);