# CLAUDE.md

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

## Commands

### Backend (from `backend/`)
```
npm run dev          # démarrage en mode développement (nodemon)
npm run migrate      # applique les migrations Knex
npm run seed         # insère les données de démonstration
npm run db:reset     # rollback complet + migrate + seed
npm test             # tests unitaires + intégration (vitest)
npm run test:watch   # vitest en mode watch
```

### Frontend (from `frontend/`)
```
npm run dev          # Vite dev server — http://localhost:5173
npm run build        # build de production (dist/)
```

### Docker (recommandé, depuis la racine)
```
docker compose up --build    # lance Postgres + backend + frontend en une commande
```
Au premier démarrage Docker, les migrations et le seed s'exécutent automatiquement.  
Compte de démo : `nyval.ship@outlook.com` / `NyvalAdmin2026!`

### Ajouter une migration
```
cd backend && npm run migrate:make nom_de_la_migration
```
Les fichiers sont créés dans `backend/src/db/migrations/`. Le rollback est dans `exports.down`.

---

## Architecture

### Vue d'ensemble
Monorepo à deux packages indépendants (`backend/`, `frontend/`). Pas de workspace npm partagé.

```
NYVAL AI/
├── docker-compose.yml
├── backend/         # Node.js + Express + Knex + PostgreSQL
└── frontend/        # React + Vite + React Query + SCSS
```

### Backend

**Point d'entrée** : `src/server.js` → `src/app.js` → `src/routes/index.js`

Toutes les routes API sont montées sous `/api` dans un seul fichier `routes/index.js`. L'ordre des middlewares y est critique :
1. Routes publiques (auth, Shopify callback)
2. `requireAuth` — vérifie le JWT ET la session active en base
3. `requireAppAccess` — vérifie le rôle plateforme (`max` ou `admin`)
4. Routes protégées avec `requireRole('editor'|'admin')` au besoin

**Double couche d'autorisation** :
- **Rôle boutique** (`req.auth.role`) : `owner > admin > editor > viewer` — contrôlé par `requireRole()` dans `rbac.js`
- **Rôle plateforme** (`req.auth.platformRole`) : `admin > max > user` — contrôlé par `requireAppAccess()` / `requirePlatformAdmin()` dans `platform.js`

**Services clés** :
| Fichier | Responsabilité |
|---|---|
| `claude.service.js` | Appels Anthropic : `optimizePrompt`, `analyze`, `suggestCollections`, `generateProductContent`. La clé API est résolue par boutique via `apiKeys.service.js`, avec repli sur `.env`. |
| `nanobanana.service.js` | Génération d'images via Google Gemini (`generativelanguage.googleapis.com`). Retry automatique sur 503/429/500. |
| `shopify.service.js` | Appels REST Shopify Admin API. |
| `sync.service.js` | Synchronisation bidirectionnelle Shopify → base SQL (produits, variantes, images, orders). |
| `marketIntelligence.service.js` | Analyse produit (catégorie, segment, prix) + base de connaissances statique + apprentissage via `content_learnings`. |
| `apiKeys.service.js` | Stocke les clés API chiffrées (AES-256-GCM) par boutique. Repli sur variables d'env si aucune clé boutique. |
| `activity.service.js` | `log()` — journalise toute action notable dans `activity_logs`. Ne fait jamais échouer la requête métier. |

**Isolation des données** : toutes les tables métier ont `shop_id`. Chaque contrôleur filtre systématiquement par `req.auth.shopId`. Ne jamais retourner de données cross-boutique.

**Gestion des erreurs** : lever `ApiError` (via `src/utils/errors.js`) depuis les contrôleurs et services — le middleware `errorHandler` le convertit en JSON structuré `{ error: { code, message } }`. Les erreurs inconnues retournent 500 "Erreur interne". Toujours utiliser `asyncHandler()` pour envelopper les handlers async.

**Versionnement** : avant chaque modification d'un produit/mannequin/visuel, un snapshot est inséré dans `product_versions` / `model_versions` / `visual_versions`.

**Meta produit** : le champ `products.meta` est du JSON stringifié contenant `images[]`, `variants[]`, `options[]`, `colorOptionKey`, `product_type`, `vendor`, `tags[]`. Toujours parser avec `JSON.parse(product.meta || '{}')`.

### Frontend

**Routing** : React Router v6. Les routes sont définies dans `src/main.jsx` (ou `App.jsx`).

**Données serveur** : React Query v5 (`@tanstack/react-query`). Pattern : `useQuery(['clé', id], () => api.get(...))` pour les lectures, `useMutation` + `queryClient.invalidateQueries` pour les mutations.

**Client HTTP** : `src/api/client.js` exporte `api` (axios configuré) et `errorMessage(err)`. Le client intercepte les 401 pour tenter un refresh automatique du JWT. Toujours lire les messages d'erreur via `e?.response?.data?.error?.message`.

**Styles** : un seul fichier SCSS global `src/styles/main.scss`. Pas de CSS Modules. Ajouter les nouveaux styles à la fin du fichier.

**AuthContext** (`src/context/AuthContext.jsx`) : expose `user`, `shop`, `login()`, `logout()`. Consommé via `useAuth()`.

**Workflow IA** (composant `PromptWorkflow`) : obligatoire pour toute génération. Séquence : saisie prompt → `POST /ai/optimize-prompt` (Claude reformule) → confirmation utilisateur → `POST /ai/generate` (routage automatique vers Claude ou Nano Banana 2).

### Base de données

Knex pour les migrations et les requêtes. Config dans `backend/knexfile.js` (racine backend).

Tables principales :
- `shops` — boutiques (entité centrale)
- `users`, `shop_members`, `shop_sessions` — auth et RBAC
- `api_keys` — clés chiffrées par boutique et provider
- `products`, `product_versions` — catalogue avec historique
- `models`, `visuals` — mannequins et visuels générés
- `collections`, `product_collections` — collections Shopify
- `content_learnings` — apprentissage Market Intelligence Engine
- `financial_transactions`, `subscriptions`, `shopify_orders` — finance
- `activity_logs` — journal d'audit

### Variables d'environnement requises

`API_KEY_ENCRYPTION_KEY` (64 hex), `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET` sont obligatoires en production. Générés avec `openssl rand -hex 32`.

Les clés IA (`ANTHROPIC_API_KEY`, `NANO_BANANA_API_KEY`) peuvent être dans `.env` (repli global) ou configurées boutique par boutique dans l'app (Paramètres → Clés API).
