# Pique-nique Finder — v2

Carte des aires de pique-nique françaises. **v2** : un serveur **Node.js / Express**
unique + **MongoDB** (source de vérité), API REST sous `/api`, frontend statique
servi depuis `./public`, et un **grabber** planifié qui alimente la base depuis
`pique-nique.info`.

Refonte du prototype PHP « deux scripts + JSON sur disque ». Spécification
complète : [`v2.md`](./v2.md).

## Architecture

Structure inspirée de `sogest-api` (JS ESM, `db.js`/`server.js` à la racine,
logique métier dans `inc/<domaine>/`, routers dans `routes/`, scripts dans `tools/`).

```
.
├── server.js              # bootstrap Express : /api/* + static ./public + cron
├── db.js                  # connexion MongoDB + création des index
├── public/                # frontend (servi à la racine "/")
│   ├── index.html
│   ├── app.js             # Leaflet + markercluster + Turf, fetch('/api/...')
│   └── styles.css
├── inc/
│   ├── core/              # config, equipements (contrat figé), http, response
│   ├── lieux/             # normalize, store (doc→liste), notes (dénormalisation)
│   └── grabber/           # departements, scrape (cheerio), index (runGrab), cron
├── routes/                # lieux, notes, equipements, admin
└── tools/                 # grab.js (grab manuel), migrate.js (import data/ legacy)
```

## Prérequis

- **Node.js ≥ 20** (fetch natif)
- **MongoDB** en écoute sur `MONGO_URI` (défaut `mongodb://localhost:27017/picnic`)

## Installation & lancement

```bash
npm install
cp .env.modele .env          # ajuster si besoin

# MongoDB (exemple docker) :
docker run -d -p 27017:27017 --name picnic-mongo mongo:7

npm run dev                  # serveur + API + static + cron (nodemon)
# ou : npm start
# → http://localhost:3000/            (frontend)
#   http://localhost:3000/api/lieux   (API)
```

## Remplir la base

```bash
npm run grab                 # grab complet one-shot (~20 min : 6045 fiches × 200 ms)
npm run migrate              # importe un ancien dossier data/ (lieux, coords, notes)
```

Le grabber tourne aussi automatiquement via cron (`CRON_SCHEDULE`, défaut 4h).
`GRAB_ON_EMPTY=true` lance un grab au démarrage si la base est vide.
`POST /api/admin/grab` (header `X-Admin-Token`) déclenche un grab manuel si
`ADMIN_TOKEN` est configuré.

## API REST (`/api`)

| Méthode | Route | Rôle |
|---|---|---|
| GET | `/api/lieux` | Liste normalisée. Params : `bbox`, `eq` (répétable, AND), `note_min`, `note_max`, `updated_since` |
| GET | `/api/lieux/:id` | Détail (ajoute `description`), `404` si inconnu |
| GET | `/api/lieux/:id/notes` | Moyenne + nombre d'avis |
| POST | `/api/lieux/:id/notes` | Ajoute un avis `{ note: 1..5 }`, renvoie moyenne recalculée |
| GET | `/api/equipements` | Contrat des 9 équipements (emoji + clé) |
| POST | `/api/admin/grab` | Déclenche un grab (protégé par `ADMIN_TOKEN`) |

## Configuration (`.env`)

Voir [`.env.modele`](./.env.modele) : `PORT`, `MONGO_URI`, `CRON_SCHEDULE`,
`GRAB_ON_EMPTY`, `DEP_TTL_DAYS`, `GRAB_THROTTLE_MS`, `CORS_ORIGIN`, `ADMIN_TOKEN`.

## Modèle de données

Trois collections (cf. `v2.md` §3) : `lieux` (dénormalisée : coords effectives,
`equipementsActifs` triés, `noteAvg`/`noteCount`), `notes` (un doc par avis,
source de vérité), `departements` (cache Phase 1 du grabber, TTL 7 j).

Le **contrat des 9 équipements** (`inc/core/equipements.js`) est figé et partagé
backend/frontend ; `Logo Pinterest` est exclu de `equipementsActifs`.
