5bf5dfce71
- 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>
371 lines
11 KiB
Markdown
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> |