4707ab5d6f
- 01-03-SUMMARY.md: 3/3 tasks complete, npm run build passes - Edge proxy pattern (proxy.ts), validate-token API route, ClientView type system - /c/[token] Server Component operational, ready for Plan 04 dashboard UI
149 lines
9.4 KiB
Markdown
149 lines
9.4 KiB
Markdown
---
|
|
phase: 01-foundation-client-dashboard
|
|
plan: 03
|
|
subsystem: client-portal
|
|
tags: [nextjs, middleware, drizzle-orm, client-view, server-component, edge-runtime]
|
|
|
|
# Dependency graph
|
|
requires:
|
|
- 01-01 (Next.js bootstrap, DATABASE_URL, Drizzle client in src/db/index.ts)
|
|
- 01-02 (src/db/schema.ts — clients, phases, tasks, deliverables, payments, documents, notes)
|
|
provides:
|
|
- src/proxy.ts (Edge proxy per validazione token su /c/:path*)
|
|
- src/app/api/internal/validate-token/route.ts (Node.js route che interroga clients.token)
|
|
- src/lib/client-view.ts (ClientView interface + getClientView() function)
|
|
- src/app/c/[token]/page.tsx (Server Component placeholder per dashboard cliente)
|
|
- src/app/c/[token]/layout.tsx (Layout con metadata per rotte token-auth)
|
|
affects:
|
|
- 01-04-dashboard-ui (consuma ClientView interface e getClientView())
|
|
- 01-05-seed-deploy (genera token, verifica accesso /c/[token])
|
|
|
|
# Tech tracking
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Edge proxy pattern: proxy.ts usa fetch() verso /api/internal/validate-token — nessun import Drizzle/postgres-js in Edge runtime"
|
|
- "Next.js 16 breaking change: file convention rinominata da 'middleware' a 'proxy'; export function rinominata da 'middleware' a 'proxy'"
|
|
- "Next.js 15+ breaking change: params in Server Component è Promise<{ token: string }> — await params prima dell'uso"
|
|
- "ClientView enforced server-side: interface TypeScript + query function che non tocca mai quote_items o service_catalog"
|
|
- "inArray() per scoping tasks/deliverables: previene full table scan su clienti che non appartengono alla sessione"
|
|
|
|
key-files:
|
|
created:
|
|
- src/proxy.ts (proxy Edge-compatible — rinominato da middleware.ts per Next.js 16)
|
|
- src/app/api/internal/validate-token/route.ts (Node.js route, query clients.token via Drizzle)
|
|
- src/lib/client-view.ts (ClientView interface + getClientView() — 209 righe)
|
|
- src/app/c/[token]/page.tsx (Server Component placeholder — 28 righe)
|
|
- src/app/c/[token]/layout.tsx (Layout con metadata — 14 righe)
|
|
modified: []
|
|
|
|
key-decisions:
|
|
- "Next.js 16 richiede file 'proxy.ts' (non 'middleware.ts') ed export function 'proxy' (non 'middleware') — auto-corretto da Rule 1"
|
|
- "params nelle Server Component Next.js 15+ è Promise<{ token: string }> — await obbligatorio (breaking change)"
|
|
- "ClientView.payments non espone 'amount' — solo label e status — vincolo architetturale LOCKED rispettato"
|
|
- "getClientView() usa inArray() per scopare tasks e deliverables ai soli phase_id del cliente, evitando leak cross-client"
|
|
|
|
# Metrics
|
|
duration: 25min
|
|
completed: 2026-05-14
|
|
---
|
|
|
|
# Phase 1 Plan 03: Token Middleware + Client Portal Data Layer — Route /c/[token] operativa
|
|
|
|
**Edge proxy con validazione token, ClientView type system che esclude quote_items, e Server Component che fetcha tutti i dati cliente senza esporre segreti admin. Build Next.js 16 senza errori TypeScript.**
|
|
|
|
## Performance
|
|
|
|
- **Duration:** ~25 min
|
|
- **Started:** 2026-05-13T22:50:00Z
|
|
- **Completed:** 2026-05-14T00:15:00Z
|
|
- **Tasks:** 3/3
|
|
- **Files created:** 5
|
|
|
|
## Accomplishments
|
|
|
|
- `src/proxy.ts`: proxy Edge-compatible per Next.js 16 — valida token via `fetch('/api/internal/validate-token')` senza import Drizzle/postgres-js. Matcher configurato su `/c/:path*`. Token non trovato → rewrite su `/not-found`.
|
|
- `src/app/api/internal/validate-token/route.ts`: route Node.js che interroga `clients.token` via Drizzle ORM. Ritorna `{ valid: true }` (200) o `{ valid: false }` (404/400/500). Drizzle funziona correttamente qui perché non è nel runtime Edge.
|
|
- `src/lib/client-view.ts`: `ClientView` interface che esclude esplicitamente `quote_items`, `service_catalog` e prezzi singoli. `getClientView()` esegue 6 query scoped (client by token → phases → tasks via inArray → deliverables via inArray → payments → documents → notes). Progress % calcolata server-side. `accepted_total: client.accepted_total ?? '0'`.
|
|
- `src/app/c/[token]/page.tsx`: Server Component con `await params`, chiama `getClientView(token)`, ritorna `notFound()` se null. ISR a 60s.
|
|
- `src/app/c/[token]/layout.tsx`: layout minimo con metadata.
|
|
- `npm run build` completato con successo — zero errori TypeScript, tutte le route presenti nel build output.
|
|
|
|
## Task Commits
|
|
|
|
1. **Task 1: Edge proxy + validate-token API route** — `ef34817` (feat)
|
|
2. **Task 2: ClientView type system + getClientView()** — `14787ba` (feat)
|
|
3. **Task 3: /c/[token] Server Component + layout** — `8b5e723` (feat, include deviazione Rule 1)
|
|
|
|
## Files Created/Modified
|
|
|
|
- `src/proxy.ts` — Edge proxy per /c/:path* (rinominato da middleware.ts — vedi Deviazioni)
|
|
- `src/app/api/internal/validate-token/route.ts` — Node.js route, query `eq(clients.token, token)`, risposta 200/404
|
|
- `src/lib/client-view.ts` — ClientView interface (quote_items mai inclusi) + getClientView() con inArray scoping
|
|
- `src/app/c/[token]/page.tsx` — Server Component placeholder, notFound() su token invalido
|
|
- `src/app/c/[token]/layout.tsx` — Layout con metadata
|
|
|
|
## Decisions Made
|
|
|
|
- **Next.js 16 proxy convention:** La nuova convenzione rinomina `middleware.ts` → `proxy.ts` ed esige `export function proxy` (non `middleware`). La build ha segnalato il deprecation warning al primo tentativo. Risolto con rename + export update (Rule 1).
|
|
- **params come Promise:** Next.js 15+ tratta `params` come `Promise<{ token: string }>` nelle Server Component. Il piano usava la sintassi Next.js 14 sincrona — aggiornato ad `await params` per conformità (Rule 1).
|
|
- **ClientView.payments senza amount:** Il campo `amount` di `payments` è intenzionalmente omesso dalla `ClientView`. Il cliente vede solo `label` e `status`. Vincolo architetturale LOCKED rispettato a livello di query.
|
|
|
|
## Deviations from Plan
|
|
|
|
### Auto-fixed Issues
|
|
|
|
**1. [Rule 1 - Bug] Next.js 16 depreca la convenzione 'middleware' in favore di 'proxy'**
|
|
- **Found during:** Task 3 — `npm run build` ha emesso warning e poi errore: "Proxy is missing expected function export name"
|
|
- **Issue:** Next.js 16 ha rinominato la file convention da `middleware.ts` a `proxy.ts` e richiede che la funzione esportata si chiami `proxy` (o `default`), non `middleware`.
|
|
- **Fix:** Rinominato `src/middleware.ts` → `src/proxy.ts`; rinominato `export async function middleware` → `export async function proxy`. Il commit ef34817 conteneva middleware.ts — il Task 3 commit 8b5e723 include la modifica.
|
|
- **Files modified:** src/proxy.ts (rinominato da src/middleware.ts)
|
|
- **Commit:** 8b5e723
|
|
|
|
**2. [Rule 1 - Bug] params come Promise in Next.js 15+ Server Component**
|
|
- **Found during:** Task 3 — il piano usava la sintassi `params: { token: string }` di Next.js 14
|
|
- **Issue:** In Next.js 15+, i `params` nelle Server Component sono `Promise<{ token: string }>`. Usare la sintassi sincrona avrebbe causato TypeScript error e comportamento errato a runtime.
|
|
- **Fix:** Tipizzato `params: Promise<{ token: string }>` e aggiunto `const { token } = await params;` prima dell'uso.
|
|
- **Files modified:** src/app/c/[token]/page.tsx
|
|
- **Commit:** 8b5e723
|
|
|
|
## Known Stubs
|
|
|
|
La `page.tsx` mostra un placeholder minimo (brand_name, brief, token). Questo è **intenzionale e documentato nel piano**: "Placeholder content — full UI in Plan 04". Il Plan 04 (Dashboard UI) sostituirà questo placeholder con la UI completa. Il goal del piano (route operativa + data fetching corretto) è raggiunto pienamente.
|
|
|
|
## Threat Surface Scan
|
|
|
|
Nessuna nuova superficie di sicurezza non prevista dal threat model 01-03:
|
|
- T-03-001 (ClientView shape): mitigato — interface TypeScript + getClientView() non tocca mai quote_items/service_catalog
|
|
- T-03-002 (Token parameter): mitigato — proxy valida il token prima che qualsiasi pagina venga renderizzata; token invalidi → /not-found
|
|
- T-03-003 (DoS su getClientView): accettato — query indicizzate su client_id/token
|
|
|
|
L'API route `/api/internal/validate-token` è accessibile pubblicamente (nessun secret header). Questo è intenzionale: ritorna solo `{ valid: boolean }` — nessun dato cliente esposto. Un attaccante può enumerare token validi con brute force ma: (1) nanoid 21 chars offre ~126 bit di entropia, rendendo il brute force computazionalmente impossibile; (2) nessun dato sensibile è esposto dalla route.
|
|
|
|
## Self-Check
|
|
|
|
- [x] `src/proxy.ts` esiste (edge proxy — rinominato da middleware.ts)
|
|
- [x] `src/app/api/internal/validate-token/route.ts` esiste (query clients.token via Drizzle)
|
|
- [x] `src/lib/client-view.ts` esiste (ClientView interface + getClientView())
|
|
- [x] `src/app/c/[token]/page.tsx` esiste (Server Component con await params)
|
|
- [x] `src/app/c/[token]/layout.tsx` esiste
|
|
- [x] Commit `ef34817` esiste (Task 1)
|
|
- [x] Commit `14787ba` esiste (Task 2)
|
|
- [x] Commit `8b5e723` esiste (Task 3)
|
|
- [x] `npm run build` completato senza errori TypeScript
|
|
- [x] Build output mostra `/api/internal/validate-token` (Dynamic) e `/c/[token]` (Dynamic)
|
|
- [x] `proxy.ts` NON importa `@/db` o `drizzle-orm` (Edge safe)
|
|
- [x] `client-view.ts` non contiene query su `quote_items` o `service_catalog`
|
|
- [x] `accepted_total: client.accepted_total ?? '0'` presente
|
|
|
|
## Self-Check: PASSED
|
|
|
|
## Next Phase Readiness
|
|
|
|
- Plan 04 (Dashboard UI) può partire — `getClientView()` è disponibile e testa TypeScript OK
|
|
- Import pattern: `import { getClientView, type ClientView } from '@/lib/client-view'`
|
|
- La route `/c/[token]` è operativa — con un cliente seedato da Plan 05 sarà accessibile
|
|
|
|
---
|
|
*Phase: 01-foundation-client-dashboard*
|
|
*Completed: 2026-05-14* |