Files
simone 5bf5dfce71 infra(04-00): route /c/ → /client/, Dockerfile, Gitea deploy
- Rename src/app/c/[token] → src/app/client/[token]
- Update proxy.ts, ClientRow, admin client detail with /client/ path
- Add output: "standalone" to next.config.ts for Docker build
- Add Dockerfile (multi-stage, node:20-alpine) and .dockerignore
- Push schema to Coolify Postgres via SSH tunnel (drizzle-kit push ✓)
- Update CLAUDE.md constraint 4 to reflect /client/ route
- Add Phase 4 planning artifacts (04-00, 04-RESEARCH, 04-PATTERNS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 16:12:05 +02:00

371 lines
11 KiB
Markdown

---
phase: 04-progetti-multi-project
plan: "00"
type: execute
wave: 0
depends_on: []
files_modified:
- src/app/client/[token]/layout.tsx
- src/app/client/[token]/page.tsx
- src/proxy.ts
- src/components/admin/ClientRow.tsx
- src/app/admin/clients/[id]/page.tsx
- Dockerfile
- .env.example
autonomous: false
must_haves:
truths:
- "La route /client/[token] esiste e risponde correttamente (cartella rinominata da /c/)"
- "src/proxy.ts controlla /client/ invece di /c/ nel guard e nel matcher"
- "ClientRow.tsx e admin/clients/[id]/page.tsx usano /client/ nei link generati"
- "Il Dockerfile produce un build Next.js funzionante (exit code 0)"
- "DATABASE_URL in .env punta al Postgres self-hosted su Coolify (non Neon)"
- "drizzle-kit push sul nuovo DB completes con exit code 0"
- "L'app risponde su hub.iamcavalli.net dopo il deploy su Coolify"
artifacts:
- path: "src/app/client/[token]/page.tsx"
provides: "Dashboard cliente alla nuova route"
contains: "export default"
- path: "src/proxy.ts"
provides: "Middleware aggiornato per /client/"
contains: "pathname.startsWith(\"/client/\")"
- path: "Dockerfile"
provides: "Container image per Coolify"
contains: "FROM node:"
key_links:
- from: "src/proxy.ts"
to: "src/app/client/[token]/"
via: "matcher: ['/admin/:path*', '/client/:path*']"
pattern: "/client/:path*"
- from: "src/components/admin/ClientRow.tsx"
to: "src/app/client/[token]/"
via: "href={`/client/${client.token}`}"
pattern: "/client/"
---
<objective>
Infrastruttura pre-Phase 4: migrazione da Neon a Postgres self-hosted su Coolify (Hetzner), rinomina route cliente da /c/ a /client/, Dockerfile per deploy, dominio hub.iamcavalli.net.
Questo piano esegue PRIMA di 04-01 per garantire che tutto il codice Phase 4 venga scritto e testato sull'infrastruttura finale. Nessun refactor post-esecuzione.
Output: App deployata su hub.iamcavalli.net con DB Postgres su Coolify, route /client/[token] funzionante.
</objective>
<context>
@/Users/simonecavalli/IAMCAVALLI/.planning/ROADMAP.md
@/Users/simonecavalli/IAMCAVALLI/CLAUDE.md
<interfaces>
<!-- Riferimenti correnti da aggiornare -->
proxy.ts riga 32: `if (pathname.startsWith("/c/"))`
proxy.ts riga 33: `pathname.match(/^\/c\/([a-zA-Z0-9_-]+)/)`
proxy.ts riga 63: `matcher: ["/admin/:path*", "/c/:path*"]`
ClientRow.tsx riga 59: `href={\`/c/${client.token}\`}`
admin/clients/[id]/page.tsx riga 46: `href={\`/c/${client.token}\`}`
Cartella da rinominare: src/app/c/ → src/app/client/
</interfaces>
</context>
<threat_model>
T-00-01: Redirect loop dopo rinomina — mitigazione: aggiornare proxy.ts prima di rinominare la cartella, verificare matcher.
T-00-02: DB connection string esposta — mitigazione: .env mai committato, .env.example con placeholder.
T-00-03: Dati di test persi durante migrazione DB — accettabile: tutti i dati attuali sono test data (CONTEXT.md).
T-00-04: Coolify deploy fallisce silenziosamente — mitigazione: verificare health check dopo deploy.
</threat_model>
<tasks>
<task id="1" type="execute" autonomous="false">
<name>Task 1: Postgres su Coolify + migrazione DATABASE_URL</name>
<read_first>
- .env (connection string Neon attuale — non committare)
- drizzle.config.ts (per confermare che legge DATABASE_URL)
</read_first>
<action>
**A. Crea Postgres su Coolify (manuale — non automatizzabile)**
Nel pannello Coolify su Hetzner:
1. New Resource → Database → PostgreSQL
2. Nome: `clienthub-db`
3. Version: 16
4. Salva le credenziali generate (host, port, user, password, dbname)
5. Abilita "Public port" se necessario per drizzle-kit push da locale
**B. Aggiorna .env locale**
Sostituisci la riga DATABASE_URL con la connection string Coolify:
```
DATABASE_URL=postgresql://USER:PASSWORD@HOST:PORT/DBNAME?sslmode=require
```
Mantieni la vecchia riga commentata come backup:
```
# DATABASE_URL=postgresql://neon... (backup)
```
**C. Aggiorna .env.example**
Sostituisci il placeholder Neon con quello generico:
```
DATABASE_URL=postgresql://user:password@host:5432/dbname?sslmode=require
```
**D. Push schema al nuovo DB**
```bash
npx drizzle-kit push
```
Il DB è fresh — nessuna migrazione soft necessaria, tutti i dati attuali sono test data.
</action>
<verify>
<automated>npx drizzle-kit push 2>&1; echo "Exit: $?"</automated>
</verify>
<acceptance_criteria>
- drizzle-kit push completes con exit code 0
- .env contiene DATABASE_URL che punta a Coolify (non neon.tech)
- .env.example non contiene credenziali reali
- npx tsc --noEmit non produce errori legati al DB
</acceptance_criteria>
</task>
<task id="2" type="execute" autonomous="true">
<name>Task 2: Rinomina route /c/ → /client/ + aggiorna reference</name>
<read_first>
- src/proxy.ts (middleware completo — LEGGERE PRIMA di modificare)
- src/components/admin/ClientRow.tsx (link generato riga 59)
- src/app/admin/clients/[id]/page.tsx (link generato riga 46)
- src/app/c/[token]/page.tsx (dashboard cliente da spostare)
- src/app/c/[token]/layout.tsx (layout da spostare)
</read_first>
<action>
**A. Rinomina cartella App Router**
```bash
mv src/app/c src/app/client
```
Questo sposta automaticamente page.tsx e layout.tsx alla nuova route.
**B. Aggiorna src/proxy.ts**
Tre sostituzioni esatte:
Riga 32 — cambia:
```typescript
if (pathname.startsWith("/c/")) {
```
in:
```typescript
if (pathname.startsWith("/client/")) {
```
Riga 33 — cambia:
```typescript
const tokenMatch = pathname.match(/^\/c\/([a-zA-Z0-9_-]+)/);
```
in:
```typescript
const tokenMatch = pathname.match(/^\/client\/([a-zA-Z0-9_-]+)/);
```
Riga 63 — cambia:
```typescript
matcher: ["/admin/:path*", "/c/:path*"],
```
in:
```typescript
matcher: ["/admin/:path*", "/client/:path*"],
```
**C. Aggiorna ClientRow.tsx**
Riga 59 — cambia:
```typescript
href={`/c/${client.token}`}
```
in:
```typescript
href={`/client/${client.token}`}
```
**D. Aggiorna admin/clients/[id]/page.tsx**
Riga 46 — cambia:
```typescript
href={`/c/${client.token}`}
```
in:
```typescript
href={`/client/${client.token}`}
```
</action>
<verify>
<automated>grep -r '"/c/' src/ --include="*.ts" --include="*.tsx" 2>&1; echo "---"; grep -r "'/c/" src/ --include="*.ts" --include="*.tsx" 2>&1; echo "---"; grep -r '/c/:path' src/ --include="*.ts" --include="*.tsx" 2>&1</automated>
</verify>
<acceptance_criteria>
- `src/app/client/[token]/page.tsx` esiste (ls conferma)
- `src/app/c/` NON esiste più (ls conferma)
- `grep '"/c/' src/ -r` produce zero risultati
- `grep "startsWith(\"/client/\")" src/proxy.ts` produce un match
- `grep "/client/:path\*" src/proxy.ts` produce un match
- `npx tsc --noEmit` exit code 0
</acceptance_criteria>
</task>
<task id="3" type="execute" autonomous="true">
<name>Task 3: Dockerfile per Coolify</name>
<read_first>
- package.json (script build, versione Node richiesta)
- next.config.ts (per verificare se output: standalone è già presente)
- .gitignore (per confermare che .env non è committato)
</read_first>
<action>
**A. Aggiungi output standalone a next.config.ts**
Next.js standalone mode produce un bundle minimale ottimale per Docker.
In next.config.ts, aggiungi dentro l'oggetto config:
```typescript
output: "standalone",
```
**B. Crea Dockerfile nella root del progetto**
```dockerfile
FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json* ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXTAUTH_URL=https://hub.iamcavalli.net
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]
```
**C. Crea .dockerignore nella root**
```
.env
.env.local
node_modules
.next
.git
.planning
```
**D. Aggiungi variabili d'ambiente su Coolify**
Nel pannello Coolify → app → Environment Variables, aggiungere:
```
DATABASE_URL=postgresql://... (la stessa del .env locale)
NEXTAUTH_SECRET=...
NEXTAUTH_URL=https://hub.iamcavalli.net
ADMIN_EMAIL=...
ADMIN_PASSWORD=...
```
**E. Configura dominio su Coolify**
Nel pannello Coolify → app → Domains:
- Aggiungi: `hub.iamcavalli.net`
- Abilita SSL automatico (Let's Encrypt)
</action>
<verify>
<automated>docker build -t clienthub-test . 2>&1 | tail -5; echo "Exit: $?"</automated>
</verify>
<acceptance_criteria>
- `Dockerfile` esiste nella root del progetto
- `.dockerignore` esiste e contiene `.env`
- `next.config.ts` contiene `output: "standalone"`
- `docker build` completes con exit code 0 (se Docker disponibile localmente)
- In alternativa: `npm run build` exit code 0 (verifica il build Next.js)
- Il file `.env` NON appare in `git status` (confermato da .gitignore)
</acceptance_criteria>
</task>
<task id="4" type="checkpoint" subtype="human-verify" autonomous="false">
<name>Checkpoint: verifica deploy su hub.iamcavalli.net</name>
<what_was_built>
- Postgres self-hosted su Coolify con schema applicato
- Route /client/[token] funzionante (rinominata da /c/)
- Dockerfile per deploy su Coolify
- Dominio hub.iamcavalli.net configurato
</what_was_built>
<how_to_verify>
1. Apri https://hub.iamcavalli.net — deve rispondere (anche con pagina 404 standard Next.js)
2. Apri https://hub.iamcavalli.net/admin/login — deve mostrare la schermata di login
3. Fai login come admin — deve accedere alla lista clienti
4. Copia il link di un cliente — deve essere nella forma `hub.iamcavalli.net/client/[token]`
5. Apri il link cliente — la dashboard deve caricare correttamente
6. Verifica che il vecchio link `/c/[token]` restituisca 404 (non più attivo)
</how_to_verify>
<resume_signal>
Digita "hub ok" quando tutti i controlli passano.
</resume_signal>
</task>
</tasks>
<verification>
1. `src/app/c/` non esiste — `src/app/client/[token]/` esiste
2. `grep -r '"/c/' src/` produce zero risultati
3. `grep "startsWith(\"/client/\")" src/proxy.ts` produce un match
4. `npx drizzle-kit push` exit code 0 sul nuovo DB
5. `npm run build` exit code 0
6. https://hub.iamcavalli.net risponde dopo deploy Coolify
7. https://hub.iamcavalli.net/client/[token] carica la dashboard cliente
</verification>
<summary_template>
## Plan 04-00 Complete
**Infrastruttura migrata:**
- DB: Neon → Postgres self-hosted su Coolify (Hetzner)
- Route: /c/[token] → /client/[token]
- Deploy: Vercel → Coolify via Docker
- Dominio: hub.iamcavalli.net attivo
**File modificati:** src/proxy.ts, src/components/admin/ClientRow.tsx, src/app/admin/clients/[id]/page.tsx, next.config.ts
**File creati:** Dockerfile, .dockerignore
**File spostati:** src/app/c/ → src/app/client/
</summary_template>