feat(10-02): add first-login-wins is_admin bootstrap in upsertUser (D-01)

- zero-admin COUNT check before INSERT: first user gets is_admin=true
- subsequent users (admin already exists) get is_admin=false
- existing-user early-return path unchanged (is_admin not modified)
- Phase-12 hook comment: tighten to first login after app_config.setup_complete
- adds 'import { sql }' from drizzle-orm
This commit is contained in:
Lucas Berger
2026-06-13 14:35:11 -04:00
parent 9e1507f7a8
commit 72e0140f01
+17 -3
View File
@@ -8,7 +8,7 @@
* Source: RESEARCH.md § "User upsert with color assignment"
*/
import { and, eq } from 'drizzle-orm';
import { and, eq, sql } from 'drizzle-orm';
import { db } from '../db/client.js';
import { users } from '../db/schema.js';
@@ -109,7 +109,20 @@ export async function upsertUser(oidcIss: string, oidcSub: string, displayName?:
COLOR_PALETTE.find((c) => !usedColors.has(c)) ??
COLOR_PALETTE[usedColors.size % COLOR_PALETTE.length];
// 3. Insert new user row
// 3. First-login-wins is_admin bootstrap (D-01).
// When zero admins currently exist, the first new user becomes admin.
// Phase 12 will tighten this to: first user after app_config.setup_complete.
// Until then, "first user when zero admins exist" is the bootstrap condition.
// This hook reads cleanly: Phase 12 adds a setup_complete check before the
// COUNT, so only first login AFTER setup is flagged — no restructuring needed.
const [{ count }] = await db
.select({ count: sql<number>`COUNT(*)` })
.from(users)
.where(eq(users.isAdmin, true))
.limit(1);
const shouldBeAdmin = Number(count) === 0;
// 4. Insert new user row
// mysql2 has no RETURNING clause — use $returningId() then re-select
const [inserted] = await db
.insert(users)
@@ -118,10 +131,11 @@ export async function upsertUser(oidcIss: string, oidcSub: string, displayName?:
oidcSub,
displayName: displayName ?? null,
color,
isAdmin: shouldBeAdmin,
})
.$returningId();
// 4. Re-select to return the full typed row
// 5. Re-select to return the full typed row
const [newUser] = await db.select().from(users).where(eq(users.id, inserted.id)).limit(1);
return newUser;