diff --git a/.planning/STATE.md b/.planning/STATE.md index 9f1903d..d78e052 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -5,33 +5,33 @@ See: .planning/PROJECT.md (updated 2026-03-04) **Core value:** ISP owners can see exactly where their money is — who owes what, what's been collected, what's been spent, and what the business actually looks like financially — in real time. -**Current focus:** Phase 1 - Foundation +**Current focus:** Phase 2 - Billing/Accounting ## Current Position -Phase: 1 of 5 (Foundation) -Plan: 4 of 5 in current phase -Status: In progress -Last activity: 2026-03-04 — Completed 01-04-PLAN.md (CASL RBAC, 5-role permission matrix, API middleware, 66 tests) +Phase: 1 of 5 (Foundation) — COMPLETE +Plan: 5 of 5 in phase 1 complete +Status: Phase 1 complete. Ready for Phase 2. +Last activity: 2026-03-04 — Completed 01-05-PLAN.md (super-admin panel, tenant management, 93 tests) -Progress: [████░░░░░░] 20% (4/20 plans across all phases) +Progress: [█████░░░░░] 25% (5/20 plans across all phases) ## Performance Metrics **Velocity:** -- Total plans completed: 4 -- Average duration: 8.75 min -- Total execution time: 35 min +- Total plans completed: 5 +- Average duration: 8.2 min +- Total execution time: 41 min **By Phase:** | Phase | Plans | Total | Avg/Plan | |-------|-------|-------|----------| -| 01-foundation | 4/5 complete | 35 min | 8.75 min | +| 01-foundation | 5/5 complete | 41 min | 8.2 min | **Recent Trend:** -- Last 5 plans: 01-01 (11 min), 01-02 (8 min), 01-03 (9 min), 01-04 (7 min) -- Trend: stable at ~8-9 min/plan +- Last 5 plans: 01-01 (11 min), 01-02 (8 min), 01-03 (9 min), 01-04 (7 min), 01-05 (6 min) +- Trend: gradually accelerating (11 → 6 min) *Updated after each plan completion* @@ -64,6 +64,10 @@ Recent decisions affecting current work: - [01-04]: Condition objects cast via any for string subjects — CASL infers MongoQuery for strings; tighten when Prisma models defined in Phase 2+ - [01-04]: Technician can("read", "Subscriber") coarse-grained — data layer enforces actual scope to assigned job contacts only - [01-04]: withPermission() HOF wraps Next.js route handlers; authorize() as convenience alias +- [01-05]: withSuperAdmin() implemented as standalone HOF (not via CASL) — super-admin access is binary, not permission-based +- [01-05]: Next.js 15 route params wrapped in Promise

— HOF awaits params before passing to handler +- [01-05]: subscriberCount hardcoded to 0 in admin API — Subscriber model added in Phase 2; API shape is forward-compatible +- [01-05]: Dual guard strategy for /admin: middleware.ts (JWT edge), layout.tsx (server), API handlers (endpoint) — three defense-in-depth layers ### Pending Todos @@ -74,9 +78,10 @@ None. - [Phase 1 research flag]: MikroTik RouterOS Node.js client library maintenance status is LOW confidence — verify `node-routeros` vs `mikronode` before implementing router integration (MikroTik integration is v2, but adapter interface should be planned) - [Phase 3 research flag]: Semaphore SMS API pricing/stability for 2026 is MEDIUM confidence — verify before any SMS work (SMS is v2, but abstraction layer design is relevant) - [01-04 note]: CASL condition types for string subjects use any cast — upgrade to class-based subjects when Phase 2 Prisma models (Subscriber, Invoice, etc.) are defined +- [01-05 note]: /admin/tenants/[id] detail page is a stub link ("View") — detail view not implemented yet (out of Phase 1 scope) ## Session Continuity -Last session: 2026-03-04T10:57:37Z -Stopped at: Completed 01-04-PLAN.md (CASL RBAC + 5-role permission matrix + API middleware + 66 tests) +Last session: 2026-03-04T11:07:04Z +Stopped at: Completed 01-05-PLAN.md (super-admin panel + tenant management + 93 total tests) Resume file: None diff --git a/.planning/phases/01-foundation/01-05-SUMMARY.md b/.planning/phases/01-foundation/01-05-SUMMARY.md new file mode 100644 index 0000000..abe9aff --- /dev/null +++ b/.planning/phases/01-foundation/01-05-SUMMARY.md @@ -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

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

— 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

> 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*