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

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
04-progetti-multi-project 00 execute 0
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
false
truths artifacts key_links
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
path provides contains
src/app/client/[token]/page.tsx Dashboard cliente alla nuova route export default
path provides contains
src/proxy.ts Middleware aggiornato per /client/ pathname.startsWith("/client/")
path provides contains
Dockerfile Container image per Coolify FROM node:
from to via pattern
src/proxy.ts src/app/client/[token]/ matcher: ['/admin/:path*', '/client/:path*'] /client/:path*
from to via pattern
src/components/admin/ClientRow.tsx src/app/client/[token]/ href={`/client/${client.token}`} /client/
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.

@/Users/simonecavalli/IAMCAVALLI/.planning/ROADMAP.md @/Users/simonecavalli/IAMCAVALLI/CLAUDE.md

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/

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

Task 1: Postgres su Coolify + migrazione DATABASE_URL

<read_first>

  • .env (connection string Neon attuale — non committare)
  • drizzle.config.ts (per confermare che legge DATABASE_URL) </read_first>
**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

npx drizzle-kit push

Il DB è fresh — nessuna migrazione soft necessaria, tutti i dati attuali sono test data.

npx drizzle-kit push 2>&1; echo "Exit: $?"

<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 2: Rinomina route /c/ → /client/ + aggiorna reference

<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>
**A. Rinomina cartella App Router**
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:

if (pathname.startsWith("/c/")) {

in:

if (pathname.startsWith("/client/")) {

Riga 33 — cambia:

const tokenMatch = pathname.match(/^\/c\/([a-zA-Z0-9_-]+)/);

in:

const tokenMatch = pathname.match(/^\/client\/([a-zA-Z0-9_-]+)/);

Riga 63 — cambia:

matcher: ["/admin/:path*", "/c/:path*"],

in:

matcher: ["/admin/:path*", "/client/:path*"],

C. Aggiorna ClientRow.tsx

Riga 59 — cambia:

href={`/c/${client.token}`}

in:

href={`/client/${client.token}`}

D. Aggiorna admin/clients/[id]/page.tsx

Riga 46 — cambia:

href={`/c/${client.token}`}

in:

href={`/client/${client.token}`}
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

<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 3: Dockerfile per Coolify

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

output: "standalone",

B. Crea Dockerfile nella root del progetto

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)
docker build -t clienthub-test . 2>&1 | tail -5; echo "Exit: $?"

<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>
Checkpoint: verifica deploy su hub.iamcavalli.net

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

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

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