Added Italian communication requirement, error-first workflow (report then wait), and missing Prisma models: ProductVariant, PasswordResetToken, Page/PageSection, SiteSettings, AuditLog. Added app/coverage/ to .gitignore.
6.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
How to work with the user
Before implementing any new feature, always discuss it first: explain whether it makes sense professionally, how it is typically done in production e-commerce platforms, and propose alternatives if there is a better approach. Only proceed with implementation after the user confirms.
Communicate in Italian. The user speaks Italian — all responses, explanations, and questions must be in Italian.
Error handling workflow: When you find an error (in logs, code, or output), report what you found and stop. Do not propose or implement a fix until the user explicitly asks. Present: what the error is, where it occurs, and what likely caused it — then wait for instructions.
Running the project
# Start everything (first run takes a few minutes to build)
docker compose up -d --build
# Check logs
docker compose logs -f app
# Stop
docker compose down
The app is available at http://localhost. Admin panel at http://localhost/admin.
Default credentials are in .env (INITIAL_ADMIN_EMAIL / INITIAL_ADMIN_PASSWORD).
Test emails are visible at http://localhost:8025 (Mailpit).
Development commands (run inside app/)
npm run dev # local dev server (port 3000, no Docker)
npm run build # production build
npx prisma studio # visual DB browser
npx prisma migrate dev --name <name> # create a new migration
npx prisma migrate deploy # apply migrations (done automatically on container start)
Architecture
Stack: Next.js 14 App Router · PostgreSQL 16 · Prisma 5 · Stripe · Nodemailer · TailwindCSS · Zod · Docker + Caddy
Source root: app/src/
| Directory | Purpose |
|---|---|
app/api/ |
REST API routes — one folder per resource |
app/admin/ |
Admin dashboard pages (role-gated at layout level) |
app/(storefront)/ |
Public-facing pages |
components/ |
Shared React components and UI primitives |
lib/ |
Core utilities (auth, email, storage, Stripe, validation, rate limiting) |
context/ |
Client-side UserContext |
Key patterns
API routes follow a consistent shape: validate input with Zod → check auth/role via getCurrentUser() → query Prisma → return JSON. Copy an existing route as a starting point.
Auth is session-based (no NextAuth). lib/auth.ts handles token creation, hashing (SHA256), and the getCurrentUser() helper used in every protected route. Sessions expire after 30 days.
Admin protection is enforced in the admin layout: CUSTOMER role is blocked, ADMIN and OWNER are allowed. A mustChangePassword flag redirects new owners to a password-change page before anything else.
Validation schemas live in lib/validate.ts (Zod). Add new schemas there; never validate inline in routes.
Image uploads go through lib/storage.ts, which validates magic bytes (JPEG/PNG/WebP/ICO) before writing to /app/public/uploads/<productId>/. Uploads are stored in a named Docker volume (uploads) shared between the app and caddy containers.
Emails are sent via lib/email.ts (Nodemailer). Locally they land in Mailpit; in production, configure SMTP env vars.
Rate limiting is IP-based and backed by the LoginAttempt DB table (10 attempts / 15 min). Logic is in lib/rate-limit.ts.
Data model (high level)
- User → has role (
CUSTOMER/ADMIN/OWNER), sessions, orders, reviews - Product → belongs to one ProductType (defines the attribute schema), many-to-many with Category, has MediaAsset, ProductVariant, Review
- ProductType → defines a JSON schema for product attributes (e.g. "clothing" has size/color)
- ProductVariant → SKU, price, stock per variante (es. taglia/colore di un prodotto)
- Category → hierarchical (self-referential
parentId), many-to-many with Product - Order → status state machine (
PENDING → PAID → FULFILLED,CANCELLED,REFUNDED), has OrderItem and Payment - PasswordResetToken → token hashed con scadenza per il flusso reset password
- Page / PageSection → CMS minimale per pagine statiche (slug, titolo, sezioni JSON ordinate)
- SiteSettings — key/value store for shop configuration (branding, etc.)
- AuditLog — tracks admin actions
Environment variables
Copy .env.example to .env. For production, generate a strong AUTH_SECRET with openssl rand -hex 32 and replace all placeholder values. The DATABASE_URL uses the Docker service name db as host — do not change it for containerised deployments.
Debugging
Check app logs (first thing to do):
docker compose logs -f app # live
docker compose logs app --tail=100 # last 100 lines
Common error patterns to look for in logs:
EACCES: permission denied→ volume/filesystem permissions issuePrismaClientKnownRequestError→ DB constraint violation or bad query401 / 403in API → session expired or role check failing- Silent form failures → always check
res.okinfetchcalls; UI may show success even on API error
API errors not surfacing in the UI are almost always caused by a missing res.ok check in the frontend fetch call. The pattern to use in every form submit:
const res = await fetch('/api/...', { method: 'POST', ... })
if (!res.ok) {
const data = await res.json().catch(() => ({}))
setError(data.error || `Errore (${res.status})`)
return
}
Database inspection:
# Open Prisma Studio (visual DB browser)
cd app && npx prisma studio
# Or connect directly
docker compose exec db psql -U ecommerce ecommerce
Rebuild after code changes:
docker compose up -d --build app # rebuild only the app container
TypeScript errors in IDE but not in build: likely a missing node_modules or stale tsconfig in the IDE. Run npm install inside app/ and restart the IDE TypeScript server.
Deployment
- Point your domain DNS to the server.
- Set
APP_URLtohttps://yourdomain.comin.env. - Edit
Caddyfile: replacelocalhostwith your domain. docker compose up -d --build
Caddy handles TLS automatically via Let's Encrypt. Ports 80 and 443 must be open on the firewall.