Le dépôt n'avait aucun test, aucun linter, aucune vérification de types.
Outillage
- ESLint 9 (flat config) sur api + les deux frontends, Ruff sur le Python
- tsc --checkJs sur l'API (pas de TypeScript, juste la vérification)
- Vitest : 401 tests JS ; pytest : 43 tests Python
- Tests de mutation (Stryker), deux profils : logique pure et API complète
- Hook pre-push `npm run check` (~17 s) — le déploiement Coolify est sur webhook,
c'est donc la seule porte de qualité avant la mise en ligne
- Workflow Forgejo Actions prêt (inerte tant qu'aucun runner n'est enregistré)
Sécurité
- Injection SQL authentifiée dans resolve-conflict : `field` était interpolé
dans le SET sans allowlist
- timingSafeEqual levait sur un jeton multi-octets (500 au lieu de 401)
- setErrorHandler écrasait tous les 4xx en 500
- .env.example : ADMIN_JWT_SECRET et ADMIN_SEED_* n'étaient documentés nulle part
alors que leur absence casse toute connexion admin
Annulation réelle des crawl_run (migration 014)
- L'API posait status='error' sans que le crawler en sache rien : le process
continuait, et son UPDATE final ne matchait plus (run réussi affiché en erreur)
- Protocole coopératif : drapeau cancel_requested lu à chaque lot, le crawler
écrit lui-même status='cancelled'
Cohérence géographique (migration 014)
- Le trigger 013 supprimait les band_locations sans purger le point dénormalisé
- L'édition admin de lat/lon n'atteignait jamais band_locations : la carte
ignorait la correction. Override step_order = -1, dans une transaction
Corrections
- limit/offset NaN → 500 au lieu de 400
- OPTIONS sans `return reply` (Fastify poursuivait le cycle de vie)
- listen() sans catch, cast ::text en dur sur les colonnes numériques
- /admin/api/logs ne renvoyait pas sa pagination
- a11y : sélecteur de langue annoncé comme liste vide (role=option manquant)
Nettoyage
- apps/web/quizz-site supprimé (sans rapport avec le projet)
- Code mort : openModal(), LANG_NAMES, double import, variables inutilisées
- .dockerignore ajoutés ; node_modules racine n'était pas gitignoré
Co-Authored-By: Claude <noreply@anthropic.com>
La carte clusterisait depuis bands.geom (un seul point par groupe). Désormais
/api/clusters source band_locations : un groupe multi-périodes (ex. Thessaloniki
puis Boston) apparaît comme plusieurs points distincts.
- migration 010: colonne geom générée (Point,4326) + index GIST sur
band_locations (évite le seq-scan sur les requêtes bbox)
- /api/clusters: FROM band_locations bl JOIN bands b, bbox via geom && envelope,
filtres (pays/statut/genre/année) sur b.*, points issus de bl.lat/lon ;
la JSON des clusters porte location_raw + step_label
- web parseBandData: expose location_raw + step_label (base pour différencier
plus tard un groupe qui a déménagé d'un groupe resté sur place)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Magyar, Dansk, Suomi, Slovenčina, Hrvatski, Slovenščina, Lietuvių, Latviešu,
Eesti, Norsk bokmål. 21 langues au total.
- locales.js: traductions complètes des ~50 clés (faq, legal inclus)
- index.html: boutons dropdown avec flag-icons (sl→si, et→ee, nb→no, da→dk)
- app.js: SUPPORTED_LANGS, LANG_FLAGS, LANG_NAMES
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sélecteur de langue : dropdown custom avec flag-icons CDN (fi fi-xx), fermeture click-outside, aria-expanded
- Admin Monitor : nouvelle vue live avec status temps réel (crawl runs, géocodeur, jobs en attente)
- Log stream incrémental : fetch uniquement les nouveaux logs via min_id, buffer 500 entrées, filtre level + recherche texte client-side, flash animation sur nouvelles lignes
- Boutons inline Annuler pour runs actifs et jobs en attente
- API : GET /admin/api/live (agrégé), POST crawl-runs/:id/cancel, POST job-triggers/:id/cancel, param min_id sur GET /logs
- Refresh configurable 3/5/10/30s, pause/reprendre, vider le buffer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Options du select : noms natifs (Français, English, Deutsch…) à la place des emojis flags
- detectLang() itère navigator.languages[] pour trouver la première langue supportée
- Fallback sur "en" au lieu de "fr" si aucune langue connue
- Fallback t() sur "en" au lieu de "fr"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- i18n front-end (fr/en/de/es/it/pl/nl/ro/pt/cs/sv) via locales.js + data-i18n attrs
- Sélecteur de langue avec drapeaux dans la topbar, persisté en localStorage
- Mentions légales et FAQ générées dynamiquement par locale (buildLegal/buildFaq)
- Section "Remerciements" : Geoapify, OSM, Leaflet, Metal Archives, HellBlazer
- Admin : centre de commandes (crawler jobs, géocodeur, maintenance, live log tail 10s)
- Admin : boutons reset-errors / requeue-all géocodeur avec confirmation
- Admin : toutes les actions admin loguées dans crawl_log pour audit
- Suppression anciens artefacts (worker, infra/, apps/api/src/index.js)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DB PostgreSQL extraite du compose (geree par Coolify avec backup R2)
- DATABASE_URL devient une variable externe injectee par Coolify
- Ajout apps/web/Dockerfile (nginx alpine servant le site statique)
- Labels Traefik: websecure->https, le->letsencrypt, reseau coolify
- Suppression code-server (remplace par workflow git)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>