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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user