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