docs: mission, decisions d'architecture, guides de deploiement et contribution
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# JWE v2 — Décisions d'architecture (Agent Architecte)
|
||||
|
||||
Date : 2026-07-16 · Mission : refonte GeoGuessr souverain (voir `projet_geoguessr_97x.md`)
|
||||
|
||||
## 1. Décision back-end : SvelteKit seul (pas de Strapi)
|
||||
|
||||
**Décision : SvelteKit seul, avec les lieux dans un fichier JSON versionné dans Git.**
|
||||
|
||||
Arguments :
|
||||
|
||||
- **Frugalité OKI** : un seul processus Node à héberger, pas de CMS + admin + DB à maintenir, pas de surface d'attaque supplémentaire. SQLite serait de toute façon peu utile pour un corpus de ~40 lieux statiques.
|
||||
- **Critère du prompt** (« qui ajoute les lieux ? ») : le corpus est éditorial, produit par l'Agent Contenu et relu. Le workflow Git (pull request = relecture + crédits photos vérifiés) est la modération. Le `docs/contribution.md` documente l'ajout d'un lieu par PR.
|
||||
- **Livrables finaux** : le guide de déploiement demandé est « SvelteKit + Tile Server » — Strapi n'y figure pas.
|
||||
- **Anti-triche** : le JSON reste côté serveur (`src/lib/server/`), jamais importé par le client ; les endpoints filtrent les champs. Impossible de faire plus simple.
|
||||
|
||||
Si un jour des contributeurs non-dev doivent saisir des lieux en masse, la porte vers Strapi reste ouverte (le schéma JSON est le content-type tout désigné), mais ce n'est pas le besoin actuel.
|
||||
|
||||
## 2. Architecture des tiles MapLibre
|
||||
|
||||
- **Rendu** : MapLibre GL JS (verrouillé), chargé en lazy (import dynamique uniquement sur les écrans de jeu / exploration).
|
||||
- **Dev / démo** : tiles vectorielles [OpenFreeMap](https://openfreemap.org) — libres, basées OpenStreetMap, sans clé API. Style **custom OKI** (JSON de style propre, dérivé du style Positron, re-teinté : mer bleu profond caraïbe, terre sable, végétation verte dense, labels sobres — pas le « bleu Google »).
|
||||
- **Prod auto-hébergée** : `tileserver-gl` + extraits `.mbtiles` OpenStreetMap des 4 régions (Guadeloupe, Martinique, Guyane, La Réunion — quelques dizaines de Mo au total). Procédure dans `docs/deploiement.md`. Le style OKI accepte l'URL des tiles par variable d'environnement (`PUBLIC_TILES_URL`), bascule sans changement de code.
|
||||
- **Contrainte géographique** : `maxBounds` = enveloppe union des 4 régions (≈ lon −63…57, lat −23…18) + `minZoom`, donc impossible de scroller en Europe/Amérique du Nord. 4 boutons « saut de région » (GUADELOUPE / MARTINIQUE / GUYANE / RÉUNION) replacent la vue sur la région. Limite connue : l'enveloppe rectangulaire inclut l'Atlantique tropical et le Brésil — inoffensif (aucun indice sur le lieu), documenté.
|
||||
- Attribution © OpenStreetMap contributors affichée.
|
||||
|
||||
## 3. Stratégie Wikipedia / Wikidata
|
||||
|
||||
Principe : **rien de stocké en base**, interroge l'API en temps réel avec cache court, côté serveur uniquement.
|
||||
|
||||
Endpoint serveur : `GET /api/wiki/[qid]` (ex. `Q3077840` = Fort Delgrès — l'exemple `Q2216838` du prompt initial était erroné, corrigé par l'Agent Contenu)
|
||||
|
||||
1. `wbgetentities` sur `www.wikidata.org` → récupère le `sitelink` frwiki (et le libellé fr en fallback).
|
||||
2. `GET https://fr.wikipedia.org/api/rest_v1/page/summary/{titre}` → `title`, `extract`, `content_urls`, `thumbnail`.
|
||||
3. Réponse normalisée : `{ qid, title, extract, url, thumbnail, description }`.
|
||||
4. Pas de sitelink fr ou erreur → `404` : le front bascule sur l'écran **Cas B « Pwen blindé »**.
|
||||
5. **Cache** : `Map` en mémoire, TTL 24 h (+ `Cache-Control: max-age=3600`). User-Agent explicite (`JWE-OKI/x.y (contact)`) comme demandé par la politique Wikimédia.
|
||||
|
||||
## 4. Arborescence du projet
|
||||
|
||||
```
|
||||
JWE/ (dépôt existant — l'ancien PHP est conservé tel quel)
|
||||
├── app/ ← nouvelle application SvelteKit 2 / Svelte 5
|
||||
│ ├── src/
|
||||
│ │ ├── lib/
|
||||
│ │ │ ├── server/
|
||||
│ │ │ │ ├── lieux.ts (accès données + sélection aléatoire + scoring)
|
||||
│ │ │ │ └── data/lieux.json (corpus 40 lieux — serveur uniquement)
|
||||
│ │ │ ├── components/ (GameMap, PhotoPanel, ResultPanel, ScoreCard…)
|
||||
│ │ │ ├── i18n/ (fr.ts, gcf.ts, store)
|
||||
│ │ │ ├── styles/oki.css (tokens couleurs OKI)
|
||||
│ │ │ └── utils/ (score.ts, geo.ts)
|
||||
│ │ ├── routes/
|
||||
│ │ │ ├── +layout.svelte (header, switch langue FR/CR)
|
||||
│ │ │ ├── +page.svelte (accueil : 4 régions, modes, Jouer)
|
||||
│ │ │ ├── jeu/[mode]/ (écran de jeu split photo/carte)
|
||||
│ │ │ ├── explorer/ (mode Aprann : carte pleine page)
|
||||
│ │ │ └── api/
|
||||
│ │ │ ├── round/+server.ts (GET : lieu aléatoire SANITISÉ)
|
||||
│ │ │ ├── guess/+server.ts (POST : {placeId, lat, lon, hints, timeMs} → score + vérité)
|
||||
│ │ │ ├── lieux/+server.ts (GET : catalogue sanitise pour Explorer)
|
||||
│ │ │ └── wiki/[qid]/+server.ts (GET : extrait Wikipedia, cache 24 h)
|
||||
│ │ ├── app.html / app.d.ts
|
||||
│ │ └── service-worker.ts (PWA)
|
||||
│ ├── static/ (manifest.webmanifest, icônes, photos éventuelles)
|
||||
│ └── package.json / svelte.config.js / vite.config.ts
|
||||
├── docs/ (architecture, déploiement, contribution, audit)
|
||||
├── content/ (sources de travail de l'Agent Contenu : crédits photos)
|
||||
└── projet_geoguessr_97x.md
|
||||
```
|
||||
|
||||
## 5. Spécifications API REST (anti-triche)
|
||||
|
||||
| Endpoint | Méthode | Payload | Réponse |
|
||||
|---|---|---|---|
|
||||
| `/api/round?region=&mode=&exclude=` | GET | — | `{ id, nom, nom_creole, categorie, difficulte, photo{url,credit,alt}, indices[] }` — **jamais** `coordonnees`, `commune`, `region`, `wikidata_id` |
|
||||
| `/api/guess` | POST | `{ id, lat, lon, hintsUsed, timeMs? }` | `{ distanceKm, score, coordonnees, commune, region, nom, wikidata_id }` |
|
||||
| `/api/lieux` | GET | — | Catalogue sanitise (sans coordonnées précises : coords arrondies à 0.05° pour l'affichage Explorer uniquement — mode sans score) |
|
||||
| `/api/wiki/[qid]` | GET | — | `{ title, extract, url, thumbnail }` ou `404` |
|
||||
|
||||
Règles :
|
||||
|
||||
- Le scoring est **recalculé côté serveur** : `score = round(5000 × e^(−d/r))`, `r` selon difficulté du lieu (50/15/5/1 km), pénalité −10 %/indice, bonus temps ≤ +20 % plafonné à 5000.
|
||||
- Distance : haversine.
|
||||
- Le client ne reçoit la vérité (coordonnées, commune, wikidata_id) **qu'après** la soumission du marqueur.
|
||||
- `exclude` : liste d'ids déjà joués (mode Défi 5 rounds, stateless — pas de session serveur nécessaire, pas de leaderboard).
|
||||
|
||||
## 6. Décisions annexes
|
||||
|
||||
- **Adapter** : `adapter-node` (auto-hébergement sobre derrière nginx/caddy).
|
||||
- **PWA** : manifest + service worker minimal (cache app shell, photos en stale-while-revalidate).
|
||||
- **i18n** : dictionnaires maison `fr` / `gcf` (créole), store Svelte, `lang` persistée en localStorage.
|
||||
- **Photos** : URLs Wikimedia Commons (Special:FilePath, `?width=1200`) vérifiées CC par l'Agent Contenu ; `<img loading="lazy">` + `srcset`.
|
||||
- **A11y / calm tech** : `prefers-reduced-motion` respecté (pas de vol de marqueur), timer informatif non anxiogène, navigation clavier (carte : flèches déplacent le marqueur, Entrée valide).
|
||||
- **Licence code** : MIT (le dépôt historique était AGPL — la refonte est un nouveau module `app/` sous MIT comme exigé par le prompt ; le `LICENSE` racine reste l'historique, un `app/LICENSE` MIT est ajouté).
|
||||
@@ -0,0 +1,87 @@
|
||||
# JWE — Guide de contribution : ajouter un lieu
|
||||
|
||||
Le corpus de lieux vit dans `app/src/lib/server/data/lieux.json`, versionné dans Git. Ajouter un lieu = une pull request. La revue de PR sert de modération éditoriale (exactitude, crédits photo, licence).
|
||||
|
||||
## 1. Règles d'or d'un bon lieu
|
||||
|
||||
- **Photographiable et identifiable** : le lieu doit avoir une photo libre de qualité (Wikimedia Commons de préférence).
|
||||
- **Localisé précisément** : coordonnées GPS au lieu près (le score Expert se joue à 1 km).
|
||||
- **Éditorialement pertinent** : monument, paysage, lieu culturel, rue ordinaire… Le jeu mélange volontairement lieux célèbres et lieux du quotidien.
|
||||
- **Lacunes assumées** : il est VOLONTAIRE d'inclure des lieux sans page Wikipédia (`wikidata_id: null`) — le jeu affiche alors un écran pédagogique « Pwen blindé » appelant à documenter le territoire. Mais c'est un choix, pas une négligence : vérifiez d'abord qu'aucune page/frwiki ni entité Wikidata n'existe.
|
||||
|
||||
## 2. Schéma du lieu
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "générez un uuid (uuidgen)",
|
||||
"nom": "Fort Delgrès",
|
||||
"nom_creole": "Fò Delgrès",
|
||||
"region": "GUADELOUPE | MARTINIQUE | GUYANE | REUNION",
|
||||
"commune": "Basse-Terre",
|
||||
"coordonnees": { "lat": 16.0036, "lon": -61.7319 },
|
||||
"categorie": "MONUMENT | LIEU",
|
||||
"difficulte": "FACILE | MOYEN | DIFFICILE | EXPERT",
|
||||
"wikidata_id": "Q3077840",
|
||||
"photo": {
|
||||
"url": "https://commons.wikimedia.org/wiki/Special:FilePath/FICHIER?width=1200",
|
||||
"credit": "© Auteur / Wikimedia Commons CC-BY-SA-4.0",
|
||||
"alt": "Description précise (sert d'alternative textuelle)"
|
||||
},
|
||||
"indices": [
|
||||
{ "niveau": 1, "texte": "Indice doux" },
|
||||
{ "niveau": 2, "texte": "Indice précis" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Trouver et lier l'identifiant Wikidata
|
||||
|
||||
1. Cherchez le lieu sur https://www.wikidata.org
|
||||
2. L'identifiant ressemble à `Q3077840`.
|
||||
3. Vérifiez que l'entité a un lien vers une page **fr.wikipedia** (sitelink) — c'est ce qui permet au jeu d'afficher l'extrait de l'article :
|
||||
|
||||
```bash
|
||||
curl -s "https://www.wikidata.org/w/api.php?action=wbgetentities&ids=Q3077840&props=sitelinks&format=json" | grep frwiki
|
||||
```
|
||||
|
||||
4. **Si le lieu n'a ni page Wikipédia ni entité Wikidata** : mettez `"wikidata_id": null`. C'est une contribution précieuse : le jeu transforme la lacune en appel à contribution. Mieux encore : créez l'entité Wikidata vous-même, puis revenez renseigner l'ID !
|
||||
|
||||
## 4. Choisir la photo (licence obligatoire)
|
||||
|
||||
1. Cherchez sur https://commons.wikimedia.org une photo nette, paysage de préférence.
|
||||
2. Licences acceptées : CC BY, CC BY-SA, CC0, domaine public. **Jamais** de « fair use ».
|
||||
3. Vérifiez la licence et l'auteur via l'API :
|
||||
|
||||
```bash
|
||||
curl -s "https://commons.wikimedia.org/w/api.php?action=query&titles=File:NOM_DU_FICHIER&prop=imageinfo&iiprop=url|extmetadata&format=json"
|
||||
```
|
||||
|
||||
4. L'URL dans le JSON utilise `Special:FilePath` avec `?width=1200` (redimensionnement à la volée côté Wikimedia — pas besoin d'héberger l'image). Testez qu'elle renvoie bien 200.
|
||||
5. Reportez le crédit exact (auteur + licence) dans `photo.credit` ET dans `content/credits.md`.
|
||||
|
||||
## 5. Valider avant la PR
|
||||
|
||||
```bash
|
||||
cd app
|
||||
node -e "const l=require('./src/lib/server/data/lieux.json');console.log(l.length,'lieux')"
|
||||
npm run check
|
||||
```
|
||||
|
||||
Checklist de revue :
|
||||
|
||||
- [ ] coordonnées cohérentes avec la commune déclarée
|
||||
- [ ] `wikidata_id` vérifié (ou `null` justifié)
|
||||
- [ ] licence photo CC vérifiée + crédit complet
|
||||
- [ ] `alt` descriptif (accessibilité)
|
||||
- [ ] 2 indices progressifs, sans donner la réponse
|
||||
- [ ] `nom_creole` renseigné quand la forme créole est attestée (sinon, reprendre le nom français — ne pas inventer)
|
||||
|
||||
## 6. Contribuer à Wikipédia / Wikidata (au-delà du jeu)
|
||||
|
||||
Si vous ajoutez un lieu « Pwen blindé », envisagez de documenter le lieu vous-même :
|
||||
|
||||
- Créer un compte et un brouillon d'article : https://fr.wikipedia.org/wiki/Aide:Brouillon
|
||||
- Créer une entité Wikidata : https://www.wikidata.org/wiki/Wikidata:Main_Page
|
||||
- Contacter les groupes Wikimédia locaux (Wikimédia France, groupes antillais/guyanais/réunionnais).
|
||||
|
||||
Chaque entité créée enrichit Wikipédia, les cartes libres… et les futurs jeux comme JWE.
|
||||
@@ -0,0 +1,111 @@
|
||||
# JWE v2 — Guide de déploiement auto-hébergé
|
||||
|
||||
Cible : un petit VPS (1 vCPU / 1 Go RAM suffit) sous Debian/Ubuntu, derrière nginx ou Caddy. Aucun service propriétaire, aucune clé API.
|
||||
|
||||
## 1. Application SvelteKit
|
||||
|
||||
```bash
|
||||
# Prérequis : Node.js >= 20 (via nodesource ou nvm)
|
||||
git clone https://codeberg.org/OKI/jwe.git
|
||||
cd jwe/app
|
||||
npm ci
|
||||
npm run build # produit app/build (adapter-node)
|
||||
```
|
||||
|
||||
Lancer en production (port 3000 par défaut) :
|
||||
|
||||
```bash
|
||||
PORT=3000 node build
|
||||
```
|
||||
|
||||
Service systemd `/etc/systemd/system/jwe.service` :
|
||||
|
||||
```ini
|
||||
[Unit]
|
||||
Description=JWE — jeu de géolocalisation OKI
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=jwe
|
||||
WorkingDirectory=/srv/jwe/app
|
||||
Environment=PORT=3000
|
||||
Environment=ORIGIN=https://jwe.o-k-i.net
|
||||
Environment=PUBLIC_TILES_URL=https://tiles.o-k-i.net/styles/oki/style.json
|
||||
ExecStart=/usr/bin/node build
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
|
||||
## 2. Reverse proxy (exemple nginx)
|
||||
|
||||
```nginx
|
||||
server {
|
||||
server_name jwe.o-k-i.net;
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
# + TLS via certbot
|
||||
```
|
||||
|
||||
## 3. Tiles cartographiques auto-hébergées (tileserver-gl)
|
||||
|
||||
Par défaut l'application utilise OpenFreeMap (libre, sans clé). Pour une souveraineté complète, hébergez vos propres tiles OSM des 4 régions.
|
||||
|
||||
### 3.1 Récupérer les extraits OpenStreetMap
|
||||
|
||||
```bash
|
||||
mkdir -p /srv/tiles && cd /srv/tiles
|
||||
# Guadeloupe, Martinique, Guyane : extraits Geofabrik
|
||||
wget https://download.geofabrik.de/north-america/guadeloupe-latest.osm.pbf
|
||||
wget https://download.geofabrik.de/north-america/martinique-latest.osm.pbf
|
||||
wget https://download.geofabrik.de/south-america/french-guiana-latest.osm.pbf
|
||||
# La Réunion
|
||||
wget https://download.geofabrik.de/africa/reunion-latest.osm.pbf
|
||||
```
|
||||
|
||||
### 3.2 Générer les .mbtiles
|
||||
|
||||
Avec [openmaptiles](https://github.com/openmaptiles/openmaptiles) (Docker requis) : générez un `.mbtiles` par région puis fusionnez-les avec `tile-join` (paquet `tippecanoe`) :
|
||||
|
||||
```bash
|
||||
tile-join -o dom-tom.mbtiles guadeloupe.mbtiles martinique.mbtiles guyane.mbtiles reunion.mbtiles
|
||||
```
|
||||
|
||||
(Alternative plus légère : extraire les 4 régions d'un `.mbtiles` Monde basse-résolution `tile-join --bbox=…` par région, puisque la carte de devinette n'a pas besoin d'un zoom très fin partout.)
|
||||
|
||||
### 3.3 Servir avec tileserver-gl
|
||||
|
||||
```bash
|
||||
docker run --rm -d --name tiles -v /srv/tiles:/data -p 8080:8080 \
|
||||
maptiler/tileserver-gl dom-tom.mbtiles
|
||||
```
|
||||
|
||||
Copiez le style OKI de l'application (`app/src/lib/map/style-oki.json`) dans `/srv/tiles/styles/oki/`, adaptez son URL de source vers votre `dom-tom.mbtiles`, puis :
|
||||
|
||||
```bash
|
||||
Environment=PUBLIC_TILES_URL=https://tiles.o-k-i.net/styles/oki/style.json
|
||||
```
|
||||
|
||||
### 3.4 Fontes et sprites
|
||||
|
||||
tileserver-gl sert aussi les fontes (glyphs) et sprites référencés par le style. Les fontes open-source utilisées par le style OKI (ex. Noto Sans) se récupèrent via `openmaptiles/fonts` ou `klokantech/tileserver-gl-fonts`.
|
||||
|
||||
## 4. Cache Wikipedia
|
||||
|
||||
Le endpoint `/api/wiki/[qid]` met en cache les extraits 24 h en mémoire. Aucune configuration requise ; pour un cache partagé entre instances, placez nginx en cache proxy devant `/api/wiki/` (respecte déjà `Cache-Control`).
|
||||
|
||||
## 5. Mise à jour
|
||||
|
||||
```bash
|
||||
cd jwe && git pull && cd app && npm ci && npm run build
|
||||
systemctl restart jwe
|
||||
```
|
||||
|
||||
L'ajout de lieux (voir `docs/contribution.md`) ne demande qu'un rebuild + restart : le corpus est embarqué dans le build.
|
||||
Reference in New Issue
Block a user