feat: session-based auth, admin roles, user management

- All logged-in users can create/edit/manage tasks (no bearer token needed)
- Added user role system (user/admin)
- Donovan's account auto-promoted to admin on startup
- Admin page: view users, change roles, delete users
- /api/me endpoint returns current user info + role
- /api/admin/* routes (admin-only)
- Removed bearer token UI from frontend
- Bearer token still works for API/bot access
This commit is contained in:
2026-01-29 01:33:18 +00:00
parent 210fba6027
commit 93746f0f71
8 changed files with 401 additions and 111 deletions

View File

@@ -1,10 +1,11 @@
import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";
import { taskRoutes } from "./routes/tasks";
import { adminRoutes } from "./routes/admin";
import { auth } from "./lib/auth";
import { db } from "./db";
import { tasks } from "./db/schema";
import { isNull, asc, sql } from "drizzle-orm";
import { tasks, users } from "./db/schema";
import { isNull, asc, sql, eq } from "drizzle-orm";
const PORT = process.env.PORT || 3100;
@@ -34,6 +35,20 @@ async function backfillTaskNumbers() {
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({
@@ -98,6 +113,27 @@ const app = new Elysia()
})
.use(taskRoutes)
.use(adminRoutes)
// 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?.message || String(error);