docs(01-05): complete super-admin panel plan

Tasks completed: 2/2
- Task 1: Super-admin API routes and middleware guard
- Task 2: Super-admin UI panel and comprehensive test harness

Phase 1 Foundation: COMPLETE (5/5 plans)
93 tests passing across auth, RBAC, tenant isolation, super-admin

SUMMARY: .planning/phases/01-foundation/01-05-SUMMARY.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kevin-asprec
2026-03-04 19:08:38 +08:00
parent 25a12effb0
commit 9d8c45ce10
2 changed files with 171 additions and 14 deletions

View File

@@ -0,0 +1,152 @@
---
phase: 01-foundation
plan: 05
subsystem: auth
tags: [super-admin, next-auth, prisma, next.js, tailwind, vitest, rbac, multi-tenant]
requires:
- phase: 01-04
provides: "CASL RBAC with withPermission() middleware pattern (super-admin is parallel middleware)"
- phase: 01-02
provides: "getCurrentUser() from session, isSuperAdmin in JWT token"
- phase: 01-01
provides: "Tenant schema with status/suspendedAt/gracePeriodEndsAt fields"
provides:
- "withSuperAdmin() HOF middleware guard (parallel to withPermission, no CASL needed)"
- "GET /api/admin/tenants — lists all tenants with userCount, subscriberCount=0"
- "GET /api/admin/tenants/[id] — tenant detail with users list"
- "POST /api/admin/tenants/[id]/suspend — suspend (PENDING_SUSPENSION + 7-day grace) or activate"
- "(super-admin)/layout.tsx — server guard + sidebar nav for /admin/* routes"
- "/admin dashboard with total/active/suspended counts"
- "/admin/tenants table with status badges, suspend/activate actions"
- "middleware.ts updated: /admin/* requires isSuperAdmin in JWT token"
- "93 total tests passing across all Phase 1 test files"
- "Second demo tenant (Test ISP 2 + admin2@demo.com) added to seed for isolation tests"
affects:
- "02-billing: super-admin API pattern for platform-level views"
- "All phases: withSuperAdmin() is the reference guard for platform endpoints"
tech-stack:
added: []
patterns:
- "withSuperAdmin(handler) HOF — mirrors withPermission() but uses isSuperAdmin instead of CASL"
- "Super-admin layout as server component with getCurrentUser() redirect guard"
- "Client component tenant table with optimistic refresh after suspend/activate actions"
- "Next.js 15 dynamic route params resolved with Promise<P> await in HOF"
key-files:
created:
- src/lib/middleware/super-admin.ts
- src/app/api/admin/tenants/route.ts
- src/app/api/admin/tenants/[id]/route.ts
- src/app/api/admin/tenants/[id]/suspend/route.ts
- src/app/(super-admin)/layout.tsx
- src/app/(super-admin)/admin/page.tsx
- src/app/(super-admin)/admin/tenants/page.tsx
- src/lib/__tests__/super-admin.test.ts
modified:
- prisma/seed.ts
- src/middleware.ts
key-decisions:
- "withSuperAdmin() implemented as standalone HOF (not via CASL) — super-admin access is binary, not permission-based"
- "Next.js 15 route params wrapped in Promise<P> — HOF awaits params before passing to handler"
- "Tenant table page is a client component — requires useEffect + fetch for suspend/activate interactivity"
- "Admin dashboard page fetches /api/admin/tenants via server-side fetch with forwarded session cookie"
- "subscriberCount hardcoded to 0 — Subscriber model added in Phase 2; API shape is forward-compatible"
- "middleware.ts redirects non-super-admin to /login on /admin/* paths — layout provides second guard layer"
patterns-established:
- "Super-admin API routes: always wrap with withSuperAdmin() — never withPermission()"
- "Dynamic route HOFs: use generic type parameter <P = Record<string, string>> for type-safe params"
- "Super-admin UI: server layout guards redirect, client pages fetch /api/admin/* endpoints"
duration: 6min
completed: 2026-03-04
---
# Phase 1 Plan 5: Super-Admin Panel Summary
**withSuperAdmin() middleware + /admin tenant management UI + 93 tests validating full Phase 1 foundation (auth, RBAC, tenant isolation, super-admin)**
## Performance
- **Duration:** 6 min
- **Started:** 2026-03-04T11:01:34Z
- **Completed:** 2026-03-04T11:07:04Z
- **Tasks:** 2
- **Files modified:** 10
## Accomplishments
- withSuperAdmin() HOF middleware guard that checks isSuperAdmin from JWT session (returns 401/403 otherwise)
- Full admin API: list all tenants, get tenant detail, suspend/activate with 7-day grace period
- /admin/* UI panel: server-guarded layout, dashboard stats, tenant table with status badges and action buttons
- 93 tests passing across all Phase 1 test files (auth 8, RBAC 66, tenant isolation 6, super-admin 11, setup 2)
- Second demo tenant (Test ISP 2 + admin2@demo.com) added to seed for cross-tenant isolation testing
## Task Commits
Each task was committed atomically:
1. **Task 1: Super-admin API routes and middleware guard** - `df40eae` (feat)
2. **Task 2: Super-admin UI panel and comprehensive test harness** - `25a12ef` (feat)
**Plan metadata:** _(docs commit follows)_
## Files Created/Modified
- `src/lib/middleware/super-admin.ts` - withSuperAdmin() HOF: 401 if no session, 403 if not isSuperAdmin
- `src/app/api/admin/tenants/route.ts` - GET all tenants with userCount, sorted by createdAt desc
- `src/app/api/admin/tenants/[id]/route.ts` - GET single tenant detail with users list
- `src/app/api/admin/tenants/[id]/suspend/route.ts` - POST suspend (PENDING_SUSPENSION + grace) or activate
- `src/app/(super-admin)/layout.tsx` - Server guard + sidebar nav (Dashboard, Tenants) + sign out
- `src/app/(super-admin)/admin/page.tsx` - Dashboard: total/active/pending/suspended counts
- `src/app/(super-admin)/admin/tenants/page.tsx` - Tenant table with status badges + suspend/activate buttons
- `src/lib/__tests__/super-admin.test.ts` - 11 tests: middleware guard (401/403/allow), suspension logic
- `prisma/seed.ts` - Added Test ISP 2 tenant + admin2@demo.com user
- `src/middleware.ts` - /admin/* requires isSuperAdmin in JWT token at edge middleware level
## Decisions Made
- **withSuperAdmin() as standalone HOF**: Super-admin access is binary (isSuperAdmin: true/false), not permission-based — using CASL for this would be over-engineering. Parallel pattern to withPermission() but simpler.
- **Next.js 15 params as Promise**: Route handler params in Next.js 15 can be a Promise. HOF awaits params before passing to handler for forward compatibility.
- **Client component for tenant table**: Suspend/activate interactivity requires useState + fetch. Server component for layout guard, client component for interactive table.
- **subscriberCount=0 placeholder**: Subscriber model is Phase 2. API shape is forward-compatible — clients can expect this field to grow.
- **Dual guard strategy**: middleware.ts provides JWT-level edge guard; layout.tsx provides server component guard; API handlers provide endpoint-level guard. Three layers for defense in depth.
## Deviations from Plan
None - plan executed exactly as written.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
Phase 1 Foundation is complete. All requirements met:
- TENANT-01: Multi-tenant architecture with tenant isolation (01-03)
- TENANT-02: RBAC with 5-role permission matrix (01-04)
- TENANT-03: Super-admin can view/manage all tenants (01-05)
- AUTH-01: JWT sessions with NextAuth credentials provider (01-02)
- AUTH-02: Tenant-scoped email uniqueness (01-01)
- AUTH-03: Super-admin has no tenant scope (01-01, 01-02)
- AUTH-04: Grace period suspension (01-01, 01-05)
- INFRA-01: Docker PostgreSQL + Redis dev environment (01-01)
- INFRA-02: 93 unit/integration tests across auth, RBAC, isolation, super-admin (01-04, 01-05)
Phase 2 (Billing/Accounting) can begin. Relevant handoffs:
- Subscriber model will use tenantId from same schema pattern as User
- JournalEntryService COA structure defined in Phase 2 (cannot retrofit)
- CASL string subject casts (any) should be upgraded to class-based subjects when Prisma models are defined
---
*Phase: 01-foundation*
*Completed: 2026-03-04*