docs(05-02): complete Subscriber Portal Auth and API plan
Tasks completed: 2/2 - Portal authentication via subscriber account number - PortalService, API routes, and integration tests (5 tests) SUMMARY: .planning/phases/05-visibility-and-client-portal/05-02-SUMMARY.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,18 +10,18 @@ See: .planning/PROJECT.md (updated 2026-03-04)
|
|||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 5 of 5 (Visibility and Client Portal)
|
Phase: 5 of 5 (Visibility and Client Portal)
|
||||||
Plan: 1 of 5 in phase 5 (24/28 total complete)
|
Plan: 2 of 5 in phase 5 (25/28 total complete)
|
||||||
Status: In progress. 05-01 complete (Dashboard Metrics API).
|
Status: In progress. 05-01 and 05-02 complete.
|
||||||
Last activity: 2026-03-05 — Completed 05-01-PLAN.md (Dashboard Metrics API — 6 tests, revenue/overdue/subscriber/cashflow/collector metrics)
|
Last activity: 2026-03-05 — Completed 05-02-PLAN.md (Subscriber Portal Auth and API — 5 tests, portal credentials provider, subscriber-scoped endpoints)
|
||||||
|
|
||||||
Progress: [████████████████████████░░░░] 86% (24/28 plans across all phases)
|
Progress: [█████████████████████████░░░] 89% (25/28 plans across all phases)
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
**Velocity:**
|
**Velocity:**
|
||||||
- Total plans completed: 17
|
- Total plans completed: 18
|
||||||
- Average duration: 10.3 min
|
- Average duration: 10.1 min
|
||||||
- Total execution time: 178 min
|
- Total execution time: 182 min
|
||||||
|
|
||||||
**By Phase:**
|
**By Phase:**
|
||||||
|
|
||||||
@@ -31,11 +31,11 @@ Progress: [███████████████████████
|
|||||||
| 02-subscriber-and-billing-core | 5/5 complete | 57 min | 11.4 min |
|
| 02-subscriber-and-billing-core | 5/5 complete | 57 min | 11.4 min |
|
||||||
| 03-operational-modules | 5/5 complete | ~65 min | ~13 min |
|
| 03-operational-modules | 5/5 complete | ~65 min | ~13 min |
|
||||||
| 04-inventory-expenses-reports | 5/5 complete | 71 min | 14.2 min |
|
| 04-inventory-expenses-reports | 5/5 complete | 71 min | 14.2 min |
|
||||||
| 05-visibility-and-client-portal | 1/5 complete | 3 min | 3 min |
|
| 05-visibility-and-client-portal | 2/5 complete | 7 min | 3.5 min |
|
||||||
|
|
||||||
**Recent Trend:**
|
**Recent Trend:**
|
||||||
- Last 10 plans: 03-02 (11 min), 03-04 (6 min), 03-05 (9 min), 04-01 (16 min), 04-03 (17 min), 04-05 (10 min), 04-02 (8 min), 04-04 (20 min), 05-01 (3 min)
|
- Last 10 plans: 03-04 (6 min), 03-05 (9 min), 04-01 (16 min), 04-03 (17 min), 04-05 (10 min), 04-02 (8 min), 04-04 (20 min), 05-01 (3 min), 05-02 (4 min)
|
||||||
- Trend: 05-01 fastest plan yet — pure service + API + tests, no schema changes
|
- Trend: Phase 5 plans executing fast — service + API + tests pattern, minimal schema changes
|
||||||
|
|
||||||
*Updated after each plan completion*
|
*Updated after each plan completion*
|
||||||
|
|
||||||
@@ -147,6 +147,11 @@ Recent decisions affecting current work:
|
|||||||
- [05-01]: Cash flow uses same approach as FinancialReportService — POSTED JE lines on revenue (4xxx) and expense (5xxx) accounts with normal balance logic
|
- [05-01]: Cash flow uses same approach as FinancialReportService — POSTED JE lines on revenue (4xxx) and expense (5xxx) accounts with normal balance logic
|
||||||
- [05-01]: getDashboardSummary runs all 5 metric methods in parallel via Promise.all
|
- [05-01]: getDashboardSummary runs all 5 metric methods in parallel via Promise.all
|
||||||
- [05-01]: Dashboard cleanup order: expenses -> vendors -> expenseCategories (custom) -> collectionAllocations -> collections -> remittances -> paymentAllocations -> payments -> invoiceLines -> invoices -> journalEntryLines -> null reversesEntryId -> journalEntries -> zoneAssignments -> subscribers -> zones -> servicePlans -> accountingPeriods -> accounts -> ticketCategories -> expenseCategories (system) -> users -> tenant
|
- [05-01]: Dashboard cleanup order: expenses -> vendors -> expenseCategories (custom) -> collectionAllocations -> collections -> remittances -> paymentAllocations -> payments -> invoiceLines -> invoices -> journalEntryLines -> null reversesEntryId -> journalEntries -> zoneAssignments -> subscribers -> zones -> servicePlans -> accountingPeriods -> accounts -> ticketCategories -> expenseCategories (system) -> users -> tenant
|
||||||
|
- [05-02]: Dual NextAuth credentials providers (staff id=credentials, portal id=portal-credentials) on same instance — additive, no change to staff auth
|
||||||
|
- [05-02]: Subscriber.passwordHash is nullable — only subscribers with a set password can log in to portal
|
||||||
|
- [05-02]: subscriberId persisted in JWT token and session — distinguishes portal users from staff users without DB lookup
|
||||||
|
- [05-02]: withPortalAuth HOF validates subscriberId in session; 401 if no session, 403 if not portal user
|
||||||
|
- [05-02]: Portal cleanup order: payments -> paymentAllocations -> invoiceLines -> invoices -> journalEntryLines -> null reversesEntryId -> journalEntries -> subscribers -> servicePlans -> tenantSettings -> accountingPeriods -> accounts -> users -> tenant
|
||||||
|
|
||||||
### Pending Todos
|
### Pending Todos
|
||||||
|
|
||||||
@@ -164,6 +169,6 @@ None.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-03-05T09:23:00Z
|
Last session: 2026-03-05T09:25:00Z
|
||||||
Stopped at: Completed 05-01-PLAN.md (Dashboard Metrics API — 6 tests, all passing).
|
Stopped at: Completed 05-02-PLAN.md (Subscriber Portal Auth and API — 5 tests, portal credentials, subscriber-scoped endpoints).
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
---
|
||||||
|
phase: 05-visibility-and-client-portal
|
||||||
|
plan: 02
|
||||||
|
subsystem: portal
|
||||||
|
tags: [portal, auth, subscriber, nextauth, credentials, invoices, payments, account]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 01-foundation
|
||||||
|
provides: NextAuth v4 with JWT strategy, withTenantContext, middleware auth exclusions
|
||||||
|
- phase: 02-subscriber-and-billing-core
|
||||||
|
provides: Subscriber, Invoice, Payment, ServicePlan models
|
||||||
|
provides:
|
||||||
|
- Portal credentials provider (accountNumber + password login)
|
||||||
|
- PortalService with subscriber-scoped data retrieval
|
||||||
|
- Portal API routes (account, invoices, payments)
|
||||||
|
- withPortalAuth middleware for portal endpoint authorization
|
||||||
|
affects: [05-03 (portal UI consumes these APIs), 05-05 (e2e tests)]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "Dual NextAuth credentials provider (staff + portal) on same instance"
|
||||||
|
- "subscriberId in JWT/session for portal user identification"
|
||||||
|
- "withPortalAuth HOF for portal-only endpoint authorization"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- src/lib/services/portal-service.ts
|
||||||
|
- src/lib/middleware/portal-auth.ts
|
||||||
|
- src/app/api/portal/account/route.ts
|
||||||
|
- src/app/api/portal/invoices/route.ts
|
||||||
|
- src/app/api/portal/payments/route.ts
|
||||||
|
- src/lib/__tests__/portal-service.test.ts
|
||||||
|
modified:
|
||||||
|
- prisma/schema.prisma
|
||||||
|
- src/lib/auth-options.ts
|
||||||
|
- src/types/next-auth.d.ts
|
||||||
|
- src/middleware.ts
|
||||||
|
|
||||||
|
# Decisions
|
||||||
|
decisions:
|
||||||
|
- id: "portal-dual-provider"
|
||||||
|
description: "Two CredentialsProviders on same NextAuth instance (id: credentials, id: portal-credentials) -- additive, no change to staff auth"
|
||||||
|
- id: "portal-password-nullable"
|
||||||
|
description: "Subscriber.passwordHash is nullable -- only subscribers with a set password can log in to portal"
|
||||||
|
- id: "portal-subscriberId-jwt"
|
||||||
|
description: "subscriberId persisted in JWT token and session -- distinguishes portal users from staff users without DB lookup"
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
metrics:
|
||||||
|
duration: "4 min"
|
||||||
|
completed: "2026-03-05"
|
||||||
|
tasks: 2
|
||||||
|
tests: 5
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 05 Plan 02: Subscriber Portal Authentication and API Summary
|
||||||
|
|
||||||
|
**Portal auth via account number + password with subscriber-scoped read-only API endpoints, 5 tests passing**
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
### Task 1: Portal Authentication
|
||||||
|
- Added `passwordHash` (nullable String) to Subscriber model in Prisma schema
|
||||||
|
- Added second CredentialsProvider (`portal-credentials`) to NextAuth config accepting accountNumber + password + tenantId
|
||||||
|
- Provider looks up subscriber by @@unique([tenantId, accountNumber]) with non-null passwordHash
|
||||||
|
- JWT callback persists `subscriberId` into token; session callback exposes it on session.user
|
||||||
|
- Extended next-auth type definitions (Session, User, JWT) with optional `subscriberId`
|
||||||
|
- Updated middleware matcher to exclude `/portal/login` and `/api/portal/auth` from auth requirement
|
||||||
|
|
||||||
|
### Task 2: PortalService and API Routes
|
||||||
|
- Created `PortalService` with three subscriber-scoped methods:
|
||||||
|
- `getPortalAccount(db, subscriberId)` -- returns profile, plan details, credit balance, billing day
|
||||||
|
- `getPortalInvoices(db, subscriberId, options)` -- paginated invoices with line items, ordered by periodStart DESC
|
||||||
|
- `getPortalPayments(db, subscriberId, options)` -- paginated payments, ordered by createdAt DESC
|
||||||
|
- Created `withPortalAuth` middleware HOF that validates subscriberId in session (401 if no session, 403 if not portal user)
|
||||||
|
- Created three portal API routes:
|
||||||
|
- `GET /api/portal/account` -- subscriber profile + plan
|
||||||
|
- `GET /api/portal/invoices` -- paginated invoices (query: page, limit)
|
||||||
|
- `GET /api/portal/payments` -- paginated payments (query: page, limit)
|
||||||
|
|
||||||
|
### Integration Tests (5/5 passing)
|
||||||
|
1. `getPortalAccount returns subscriber with plan details` -- verifies all fields including plan name, speed, price, billingType
|
||||||
|
2. `getPortalInvoices returns paginated invoices` -- creates 3 invoices, verifies page 1 limit 2 returns 2 with total 3
|
||||||
|
3. `getPortalPayments returns paginated payment history` -- creates 2 payments, verifies list and subscriber scoping
|
||||||
|
4. `getPortalAccount scoped to subscriberId only` -- creates 2 subscribers, verifies each sees only own data
|
||||||
|
5. `getPortalInvoices includes invoice line items` -- verifies lines array populated with description and lineTotal
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None -- plan executed exactly as written.
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
| Hash | Message |
|
||||||
|
|------|---------|
|
||||||
|
| 539564d | feat(05-02): portal authentication via subscriber account number |
|
||||||
|
| 7222dbe | feat(05-02): portal service, API routes, and integration tests |
|
||||||
|
|
||||||
|
## Requirements Satisfied
|
||||||
|
|
||||||
|
- **PORT-01**: Subscriber can log in with account number and password
|
||||||
|
- **PORT-02**: Subscriber can view current bill and outstanding balance (via invoices endpoint + account creditBalance)
|
||||||
|
- **PORT-04**: Subscriber can view full payment history (via payments endpoint)
|
||||||
|
- Account overview includes plan details, balance, and billing day
|
||||||
|
- Invoice and payment history are paginated
|
||||||
|
- Portal API endpoints scoped to logged-in subscriber only
|
||||||
Reference in New Issue
Block a user