# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project

**Viață la Țară** — a Node.js + Express + MongoDB REST API backend, companion to a Flutter mobile app.
Build plan and API contract live in `PLAN.md`. Code is generated **one phase at a time**; wait for "confirm" between phases.

## Locked decisions

| # | Decision |
|---|---|
| D1 | **Auth:** native email/password + backend-issued JWTs (access + rotating refresh). Firebase only for Google/Apple sign-in (ID token exchange) + FCM push. Clients always send `Authorization: Bearer <backendAccessToken>` — never a Firebase token. |
| D2 | **Language:** TypeScript |
| D3 | **Package manager:** npm |
| D4 | **Node:** 20 LTS (or 22 LTS) |
| D5 | **Realtime:** STOMP-over-WS (`ws` + minimal STOMP frame layer) to match Flutter `@stomp/stompjs` |
| D6 | **Facebook login:** deferred (one-line toggle via Firebase Facebook provider on the existing `/api/auth/social` endpoint) |

## Tech stack

| Concern | Choice |
|---|---|
| Framework | Express 4.x |
| DB / ODM | MongoDB + Mongoose 8 |
| Validation | Zod — one schema per endpoint (body / params / query), enforced by middleware |
| Password hashing | argon2id |
| JWT | jsonwebtoken (HS256 default; RS256 optional) |
| Social auth + push | firebase-admin |
| Security | helmet, cors (allowlist, no `*`), express-rate-limit, express-mongo-sanitize, hpp, compression, body-size limits |
| Logging | pino + pino-http (request IDs, no secrets) |
| Config | dotenv + typed config module (fail fast on missing vars) |
| Tests | Vitest + Supertest |
| Dev tooling | ESLint + Prettier + tsx (dev runner) + tsc/tsup (build) |

## npm scripts (once scaffolded)

```bash
npm run dev        # tsx watch — hot reload
npm run build      # tsc/tsup to dist/
npm start          # node dist/server.js
npm run lint       # ESLint
npm run format     # Prettier
npm run typecheck  # tsc --noEmit
npm test           # Vitest
npm run seed       # populate categories
```

Run a single test file: `npx vitest run test/auth.test.ts`

## Architecture

Layered, feature-first:

```
src/
  core/           # shared spine (no business logic)
    config/       # typed env loading + Zod validation, fail-fast
    db/           # Mongoose connect, retry, graceful shutdown
    auth/         # jwt sign/verify, token TTLs, refresh rotation + reuse detection
    firebase/     # firebase-admin init, verifyIdToken, FCM send
    middleware/   # authRequired, authOptional, requireRole, requireOwnership,
                  # validate(zodSchema), errorHandler, notFound, rateLimiters
    errors/       # AppError subclasses keyed to spec error codes
    http/         # envelope helpers: ok(), created(), paginated(), fail()
    logger/       # pino instance + pino-http
    security/     # helmet/cors/sanitize/hpp/compression assembly
  modules/        # one directory per feature
    auth/         # register, login, refresh, logout, social-exchange
    users/        # /users/me
    devices/      # FCM push tokens
    categories/
    products/
    producers/
    reviews/
    favorites/
    cart/
    bookingPlaces/
    myLocations/  # business: own booking places
    partner/      # partner application
    shop/         # business: own producer + own products
    chat/         # threads + messages (REST)
    admin/        # moderation (products, producers, applications, bookingPlaces)
    realtime/     # STOMP-over-WS server
  app.ts
  server.ts
```

Each `modules/<feature>/` contains:
`feature.routes.ts → feature.controller.ts → feature.service.ts → feature.repository.ts`
plus `feature.model.ts` (Mongoose schema), `feature.schema.ts` (Zod), `feature.mapper.ts` (`_id → id`, ISO dates, strip internals).

## API contract

- **Envelope:** all responses are `{ data, error }`. Helpers in `core/http/`.
- **Error codes:** `BAD_REQUEST`, `VALIDATION_ERROR`, `UNAUTHENTICATED`, `FORBIDDEN`, `NOT_FOUND`, `CONFLICT`, `UNPROCESSABLE`, `INTERNAL_ERROR`.
- **Validation errors:** HTTP 422 with `details[] = { field, message }`.
- **Pagination:** `{ data: { items, total, page, size }, error: null }`.
- **IDs:** always `id` (not `_id`) in API responses; mappers must strip `_id`, `__v`, `passwordHash`.
- **Roles:** `client | business | admin`. Role comes from the verified backend access token; never trust client-supplied role.

## Auth flow summary

1. **Email/password:** `POST /api/auth/register` or `/login` → returns `{ accessToken, refreshToken }`.
2. **Google/Apple:** client signs in with Firebase, sends Firebase ID token to `POST /api/auth/social` → backend verifies it, find-or-creates native `User`, returns backend tokens.
3. **Refresh:** `POST /api/auth/refresh` — validates refresh token against **hashed** copy in DB, rotates (new pair, old invalidated). Reuse detection: replayed revoked token → entire session family revoked → 401.
4. **Role change (admin approves partner application):** sets `User.role = business` in DB, creates `Producer`, sends FCM `{ data: { action: "REFRESH_TOKEN" } }` → app calls refresh → new access token carries updated role.

## Security invariants

- Never trust client-supplied `id` or `role`; always derive from verified token, re-check from DB on sensitive ops.
- Refresh tokens stored **hashed** in `RefreshToken` collection; never returned in plaintext after issuance.
- Stack traces and secrets must never appear in API responses or logs; generic `500 INTERNAL_ERROR` to clients in prod.
- `express-mongo-sanitize` + Mongoose schemas block NoSQL injection; no raw user input in query keys.
- Login endpoint: rate-limited + account lockout after N failures.
- CORS: explicit allowlist, never `*` with credentials.

## Local dev

Docker Compose provides MongoDB. Firebase service-account JSON and all secrets go in `.env` (gitignored). `.env.example` documents all required vars.
