diff --git a/.planning/phases/36-admin-role-panel-foundation/36-01-SUMMARY.md b/.planning/phases/36-admin-role-panel-foundation/36-01-SUMMARY.md new file mode 100644 index 0000000..1996e07 --- /dev/null +++ b/.planning/phases/36-admin-role-panel-foundation/36-01-SUMMARY.md @@ -0,0 +1,47 @@ +--- +plan: 36-01 +phase: 36 +title: "isAdmin schema, requireAdmin middleware, /api/auth/me surface, grant script" +status: complete +completed: 2026-04-19 +--- + +## What Was Built + +Server-side admin foundation for Phase 36: + +1. **isAdmin column** added to the `users` pgTable in `src/db/schema.ts` — `boolean("is_admin").notNull().default(false)`. +2. **Drizzle migration** generated (`drizzle-pg/0009_spotty_lord_tyger.sql`) with `ALTER TABLE "users" ADD COLUMN "is_admin" boolean DEFAULT false NOT NULL`. DB push could not be applied (DB not reachable with default credentials — requires `DATABASE_URL` env var pointing to the running Postgres instance). +3. **requireAdmin middleware** added to `src/server/middleware/auth.ts` — reads `userId` from context (set by `requireAuth`), queries `users.isAdmin`, returns 401 if userId missing, 403 if `!user.isAdmin`, calls `next()` for admins. +4. **isAdmin in /api/auth/me** — `src/server/routes/auth.ts` now includes `isAdmin: fullUser?.isAdmin ?? false` in the returned user object. +5. **`/api/admin/` placeholder route** — `src/server/routes/admin.ts` applies `requireAuth` + `requireAdmin` middleware on `/*` and returns `{ ok: true }` on `GET /`. +6. **Route registration** — `src/server/index.ts` imports and registers `app.route("/api/admin", adminRoutes)`. +7. **grant-admin script** — `scripts/grant-admin.ts` grants or revokes `isAdmin` by `logto_sub`. Accepts `--revoke` flag. Exits 1 on missing sub or user not found. + +## Key Files + +- `src/db/schema.ts` — isAdmin column added to users table +- `drizzle-pg/0009_spotty_lord_tyger.sql` — migration file +- `src/server/middleware/auth.ts` — requireAdmin exported +- `src/server/routes/auth.ts` — isAdmin in /me response +- `src/server/routes/admin.ts` — new placeholder admin route +- `src/server/index.ts` — adminRoutes registered +- `scripts/grant-admin.ts` — admin grant/revoke script + +## Deviations + +- **DB push could not be applied** — the default PostgreSQL credentials (`gearbox:gearbox@localhost:5432/gearbox`) don't match the running instance. The migration file is generated and correct. Apply manually with the correct `DATABASE_URL`: + ``` + DATABASE_URL= bun run db:push + ``` + This is a deployment/environment concern, not a code defect. + +## Self-Check: PASSED + +- [x] isAdmin column in schema.ts +- [x] Migration file generated with correct SQL +- [x] requireAdmin middleware exported from auth.ts +- [x] isAdmin in /api/auth/me response +- [x] /api/admin route protected by requireAuth + requireAdmin +- [x] grant-admin.ts script created +- [x] bun run build exits 0