# 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 ```bash # 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/`) ```bash 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 # 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//`. 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):** ```bash 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 issue - `PrismaClientKnownRequestError` → DB constraint violation or bad query - `401 / 403` in API → session expired or role check failing - Silent form failures → always check `res.ok` in `fetch` calls; 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: ```ts 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:** ```bash # 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:** ```bash 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 1. Point your domain DNS to the server. 2. Set `APP_URL` to `https://yourdomain.com` in `.env`. 3. Edit `Caddyfile`: replace `localhost` with your domain. 4. `docker compose up -d --build` Caddy handles TLS automatically via Let's Encrypt. Ports 80 and 443 must be open on the firewall.