docs(01-02): complete Drizzle schema plan — 10 tables live on Postgres
- SUMMARY documenta schema, deviazioni (uuid->text+nanoid, drizzle-kit env), threat surface e self-check PASSED - Piano 03 (Middleware + /c/[token]) può partire
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
---
|
||||
phase: 01-foundation-client-dashboard
|
||||
plan: 02
|
||||
subsystem: database
|
||||
tags: [drizzle-orm, postgres, schema, migrations, nanoid]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- 01-01 (drizzle-kit, postgres-js driver, DATABASE_URL in .env.local)
|
||||
provides:
|
||||
- src/db/schema.ts con 10 tabelle complete
|
||||
- TypeScript types esportati per tutte le entità (Client, Phase, Task, ecc.)
|
||||
- Migration file SQL in src/db/migrations/
|
||||
- Schema live su Postgres 16 (Hetzner/Coolify)
|
||||
affects:
|
||||
- 01-03-client-route (usa clients, phases, tasks, deliverables, payments, documents, notes)
|
||||
- 01-04-dashboard-ui (usa tutti i types esportati)
|
||||
- 01-05-seed-deploy (inserisce dati con i types NewClient, NewPhase, ecc.)
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "ID strategy: text + nanoid() via $defaultFn (non uuid() nativo Postgres) — nanoid genera stringhe 21-char URL-safe, non UUID formato xxxxxxxx-xxxx-xxxx"
|
||||
- "drizzle-kit push richiede DATABASE_URL passata esplicitamente come env var (non carica .env.local automaticamente)"
|
||||
- "Relations Drizzle definite per tutti gli FK — usabili in query con with: { ... }"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- src/db/schema.ts (245 righe — 10 tabelle + relations + TypeScript types)
|
||||
- src/db/migrations/0000_pretty_typhoid_mary.sql (migration SQL completa)
|
||||
- src/db/migrations/meta/ (drizzle-kit metadata)
|
||||
- src/db/migrations/relations.ts (relazioni per introspect)
|
||||
- src/db/migrations/schema.ts (schema per introspect)
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Usato text + $defaultFn(() => nanoid()) invece di uuid().defaultRandom() — nanoid genera ID URL-safe crittograficamente sicuri (21 char, ~126 bit entropia), non UUID formato PostgreSQL"
|
||||
- "drizzle.config.ts dal Plan 01 già corretto (defineConfig + dialect postgresql + url:) — nessuna modifica necessaria"
|
||||
- "clients.token: text notNull unique con nanoid — separato dall'id PK, rotabile con single UPDATE"
|
||||
- "drizzle-kit push richiede DATABASE_URL come env var esplicita (non auto-load .env.local)"
|
||||
|
||||
# Metrics
|
||||
duration: 15min
|
||||
completed: 2026-05-13
|
||||
---
|
||||
|
||||
# Phase 1 Plan 02: Drizzle Schema + Migration — 10 tabelle live su Postgres
|
||||
|
||||
**Schema Drizzle ORM completo con 10 tabelle, migration SQL generata e schema live sul database Postgres 16 (Hetzner/Coolify). TypeScript strict compila senza errori.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~15 min
|
||||
- **Started:** 2026-05-13T20:21:00Z
|
||||
- **Completed:** 2026-05-13T20:36:00Z
|
||||
- **Tasks:** 3/3
|
||||
- **Files modified:** 6
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `src/db/schema.ts` creato con 10 tabelle complete + relations Drizzle + TypeScript types esportati
|
||||
- Vincoli architetturali LOCKED rispettati: `clients.token` separato dall'id PK (unique, notNull, nanoid), `accepted_total` denormalizzato, `approved_at` nullable (audit trail immutabile), `quote_items` mai esposto al client API
|
||||
- Migration SQL (`0000_pretty_typhoid_mary.sql`) generata con tutti i `CREATE TABLE` e FK constraints
|
||||
- `npx drizzle-kit push` eseguito con successo — tutte e 10 le tabelle create su `postgresql://178.104.27.55:5432/clienthub`
|
||||
- Verifica via `information_schema.tables`: clients, comments, deliverables, documents, notes, payments, phases, quote_items, service_catalog, tasks
|
||||
|
||||
## Task Commits
|
||||
|
||||
1. **Task 1: Drizzle schema (src/db/schema.ts)** - `1bdbe7a` (feat)
|
||||
2. **Task 2: Migration generation (drizzle-kit generate)** - `a6ec599` (chore)
|
||||
3. **Task 3: [BLOCKING] drizzle-kit push → Postgres live** - `abcbb52` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `src/db/schema.ts` — 10 tabelle: clients (token separato + accepted_total), phases, tasks, deliverables (approved_at nullable), comments (polimorfici), payments (da_saldare/inviata/saldato), documents, notes, service_catalog, quote_items
|
||||
- `src/db/migrations/0000_pretty_typhoid_mary.sql` — Migration SQL completa con CREATE TABLE + FK + UNIQUE constraint su token
|
||||
- `src/db/migrations/meta/` — Drizzle-kit metadata (snapshot JSON)
|
||||
- `src/db/migrations/relations.ts` — Relations per introspect
|
||||
- `src/db/migrations/schema.ts` — Schema per introspect
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- **ID strategy:** `text + $defaultFn(() => nanoid())` invece di `uuid().defaultRandom()`. La colonna Drizzle `uuid()` si aspetta il formato PostgreSQL `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, mentre `nanoid()` genera stringhe 21-char URL-safe. Usare `text` è corretto e allineato con la decisione architetturale di token crittograficamente sicuro.
|
||||
- **drizzle.config.ts invariato:** La versione dal Plan 01 usa già `defineConfig`, `dialect: "postgresql"` e `url:` (sintassi aggiornata drizzle-kit v0.31) — nessuna modifica necessaria rispetto alla versione suggerita nel piano (che usava l'API obsoleta `driver: 'pg'`).
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] uuid() non compatibile con nanoid() come defaultFn**
|
||||
- **Found during:** Task 1
|
||||
- **Issue:** Il piano suggeriva `uuid('id').primaryKey().defaultValue(nanoid())` ma Drizzle `uuid()` si aspetta UUID nel formato `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`. nanoid() genera stringhe come `Tcyf3muFXVOX9QO9pBUES` (21 char, non UUID validi). Usare `defaultValue(nanoid())` su una colonna `uuid()` avrebbe causato errori a runtime al primo INSERT.
|
||||
- **Fix:** Cambiato a `text('id').primaryKey().$defaultFn(() => nanoid())` per tutte le PK e per il campo `token`. Semantica identica (ID crittograficamente sicuro), tipo colonna SQL `text` invece di `uuid`.
|
||||
- **Files modified:** src/db/schema.ts
|
||||
- **Commit:** 1bdbe7a
|
||||
|
||||
**2. [Rule 1 - Bug] drizzle.config.ts dal piano usa API obsoleta**
|
||||
- **Found during:** Task 2
|
||||
- **Issue:** Il piano suggeriva `driver: 'pg'` e `dbCredentials: { connectionString: ... }` — sintassi drizzle-kit <0.30. Il file esistente usa già `defineConfig` con `dialect: "postgresql"` e `dbCredentials: { url: ... }` — sintassi corretta per drizzle-kit 0.31.
|
||||
- **Fix:** Mantenuto il file esistente senza modifiche (era già corretto).
|
||||
- **Files modified:** nessuno
|
||||
- **Commit:** nessuno necessario
|
||||
|
||||
**3. [Rule 3 - Blocking] drizzle-kit push non carica .env.local automaticamente**
|
||||
- **Found during:** Task 3
|
||||
- **Issue:** `npx drizzle-kit push` fallisce con "connection url required" perché drizzle-kit non carica `.env.local` automaticamente (solo `.env`).
|
||||
- **Fix:** Passato `DATABASE_URL` esplicitamente come variabile d'ambiente al comando: `DATABASE_URL="..." npx drizzle-kit push`.
|
||||
- **Files modified:** nessuno (solo comando di esecuzione)
|
||||
- **Commit:** abcbb52
|
||||
|
||||
## Known Stubs
|
||||
|
||||
Nessuno. Il piano è infrastrutturale (schema + DB) — nessun componente UI o dato presentato al cliente. Le tabelle sono vuote, ma questo è intenzionale: il seed script è previsto nel Plan 05.
|
||||
|
||||
## Threat Surface Scan
|
||||
|
||||
Il threat model T-02-001 (unicità token) è mitigato: `CONSTRAINT "clients_token_unique" UNIQUE("token")` è attivo nel database. T-02-002 e T-02-003 sono accettati come da piano.
|
||||
|
||||
Nessuna nuova superficie di sicurezza non prevista dal threat model.
|
||||
|
||||
## Self-Check
|
||||
|
||||
- [x] `src/db/schema.ts` esiste (245 righe, 10 tabelle pgTable + relations + types)
|
||||
- [x] `src/db/migrations/0000_pretty_typhoid_mary.sql` esiste con 10 CREATE TABLE
|
||||
- [x] Commit `1bdbe7a` esiste (schema)
|
||||
- [x] Commit `a6ec599` esiste (migrations)
|
||||
- [x] Commit `abcbb52` esiste (push)
|
||||
- [x] 10 tabelle verificate live su Postgres via `information_schema.tables`
|
||||
- [x] `clients.token` è `text NOT NULL UNIQUE` con nanoid — separato dalla PK
|
||||
- [x] `approved_at` è `timestamp with time zone` nullable
|
||||
- [x] TypeScript strict: `npm run build` — zero errori TypeScript
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- Plan 03 (Middleware + route `/c/[token]`) può partire — lo schema è live e i types sono importabili
|
||||
- Import pattern: `import { clients, phases, tasks, ... } from '@/db/schema'`
|
||||
- Import types: `import type { Client, Phase, Task, ... } from '@/db/schema'`
|
||||
|
||||
---
|
||||
*Phase: 01-foundation-client-dashboard*
|
||||
*Completed: 2026-05-13*
|
||||
Reference in New Issue
Block a user