Commit graph

8 commits

Author SHA1 Message Date
c39a8513f8 feat(geocoding): pipeline multi-étapes avec LLM Groq free tier
- Migration 008 : tables band_locations (N steps × M villes par band)
  et llm_cache (évite les double-appels LLM par sha256)
- parser.py : parse location_text en steps structurés, gère N/A/Unknown,
  villes multiples (Bergen / Oslo), hiérarchies admin, codes pays
- enqueue.py rewrite : peuple band_locations depuis bands, résout les
  is_country_only avec centroïdes hardcodés (confidence=0.1)
- worker.py rewrite : fallbacks progressifs Geoapify (plus spécifique
  → plus vague), sync bands.lat/lon depuis le step le plus récent,
  bascule en llm_needed après 3 échecs
- groq_worker.py (nouveau) : Groq free tier JSON mode, llm_cache,
  rate-limit par modèle, backoff exponentiel, fallback 8B si 70B saturé
- docker-compose : geocoder-enqueue (one-shot), groq-worker (continu),
  geocoder-worker devient unless-stopped
- Admin API : /geocoding retourne stats band_locations + llm_cache ;
  nouvelles routes /locations/reset-errors /reset-llm /requeue-all
- Admin UI : page Géocodage affiche les deux pipelines en parallèle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 21:49:22 +02:00
68e483762e feat: flag-icons dropdown + admin live monitor
- 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>
2026-07-01 21:02:13 +02:00
6a1b2c3e44 feat: i18n front-end (11 langues), centre de commandes admin, remerciements légaux
- 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>
2026-07-01 20:41:41 +02:00
c90b5063f1 feat: locked_fields, genres splittés, conflits, géocodage, jobs dashboard
Migration 007 :
- bands.locked_fields : champs édités manuellement = ne jamais réécrire
- bands.crawler_pending : valeurs MA différentes des valeurs verrouillées
- job_triggers : déclenchement de jobs depuis l'admin sans docker socket
- DELETE non-EU bands (6852 bands US/BR/CA/… entrés via crawl incrémental)

Crawler :
- upsert_band_enriched respecte locked_fields et stocke crawler_pending
- run_incremental filtre les bands hors Europe (EU-only désormais confirmé)
- update_crawl_run_progress toutes les 10 bands (granularité améliorée)
- main loop consomme job_triggers (enrich / incremental / full_crawl)
- retry sur JSONDecodeError dans get_json (FlareSolverr corps vide)

Admin PATCH bands :
- les champs édités sont automatiquement ajoutés à locked_fields

Dashboard :
- Genres découpés en mots-clés (Doom/Death Metal → Doom, Death Metal…)
- Listes pays/statut/genre sans limite, avec scroll interne

Bands admin :
- Colonne Thèmes + champ recherche thèmes_q
- Indicateur 🔒 sur les bands avec champs verrouillés

Nouvelles pages admin :
- Conflits : résolution champ par champ (garder ma valeur / accepter MA)
- Géocodage : stats queue, barre de progression, 20 derniers géocodages
- Jobs : boutons pour déclencher enrich/incremental/full_crawl manuellement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 23:06:20 +02:00
3dc483bc4f feat: geocoder Geoapify + fixes crawler + améliorations admin dashboard
Geocoder → Geoapify (bcb790b9007644d1a44ff2391118479c) :
- Remplace Nominatim par Geoapify dans apps/geocoder/src/worker.py
- Délai réduit à 0.22s (5 req/s vs 1 req/s Nominatim) → bien plus rapide
- Même logique de cache geocode_cache, même fallback backoff progressif
- Env vars : GEOAPIFY_API_KEY (obligatoire), GEOCODER_MIN_DELAY/JITTER

Fix crawler incremental_modified (Expecting value: line 1 column 1) :
- ma_http.get_json() retenait sans retry sur JSONDecodeError (corps vide =
  session Chrome morte). Désormais : refresh session + retry, comme pour
  les erreurs 403/429.

Admin dashboard :
- Bands : colonne Lieu, champ recherche lieu séparé (location_q), filtres
  "Géocodé oui/non" (has_lat) et "Lieu vide/renseigné" (has_location)
- Queue : bouton "Annuler runs bloqués >30min" (POST /admin/api/crawl-runs/
  cleanup), auto-refresh 15s, colonne Progression avec durée elapsed pour
  les runs actifs
- Dashboard : toutes les listes pays/genre/statut sans limite (scroll interne)
- Progression live : crawler écrit les stats dans crawl_run toutes les 50
  bands (update_crawl_run_progress), visible dans Queue en temps réel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:44:28 +02:00
10ab0055d5 fix(admin): alias réseau dédié pour éviter la collision DNS prod/dev sur "api"
Root cause du 404 intermittent : les stacks prod et dev déclarent tous les
deux un service nommé "api" attaché au même réseau externe coolify. Docker
DNS répond aléatoirement avec l'IP du conteneur prod OU dev pour le nom
"api" — quand admin/nginx tombait sur le mauvais conteneur (inaccessible
depuis cet environnement), ça donnait "connect() failed: Connection
refused" puis 404 côté client. Le resolver dynamique (commit précédent)
ne corrige pas cette collision, il la rend juste plus visible/intermittente.

Fix : alias réseau unique par environnement (bm-api-internal en prod,
dev-api-internal en dev) sur le service api, et apps/admin/Dockerfile
prend un ARG API_UPSTREAM substitué par sed dans nginx.conf au build,
pointant vers l'alias correspondant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:27:47 +02:00
def2b1214f fix(admin): résolution DNS dynamique du upstream api dans nginx
nginx résolvait "api" une seule fois au démarrage et gardait l'IP en
cache indéfiniment (comportement par défaut pour un proxy_pass avec
hostname statique). Après un redeploy Coolify du service api (nouveau
conteneur = nouvelle IP), nginx continuait à taper sur l'ancienne IP
morte -> "connect() failed (111: Connection refused)" -> 404 côté
client. Fix : resolver 127.0.0.11 (DNS Docker embarqué) + proxy_pass
via variable pour forcer une résolution à chaque requête.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:21:37 +02:00
a7d7bc94de feat(admin): dashboard admin complet (auth forte, API, monitoring)
Nouveau service apps/admin (admin.metalfrom.eu / admin.dev.metalfrom.eu) :
- Frontend statique vanilla JS/CSS reprenant le design system du site
  (login, dashboard stats, table bands éditable, queue d'enrichissement,
  historique crawl_run, logs live, checkpoints, journal d'audit)
- nginx reverse-proxy /admin/api/* et /admin/auth/* vers le service api
  interne (same-origin côté navigateur, pas de CORS cross-site nécessaire
  pour le cookie de session)

apps/api :
- Nouvelle auth dédiée au dashboard, séparée du token BM_IMPORT_TOKEN
  existant : login bcrypt + session JWT en cookie httpOnly/secure/
  sameSite=strict, rate-limit + lockout après 5 échecs/15min, seeding
  du compte admin via env vars (jamais de mot de passe en clair en DB
  ou en git)
- Routes /admin/api/* : stats, queue (breakdown priorité identique au
  crawler Python), bands (recherche/tri/pagination/édition + audit log),
  crawl-runs, crawl-checkpoints, logs, audit-log
- trustProxy activé (Traefik + nginx en amont)

apps/crawler :
- log_event() écrit dans la nouvelle table crawl_log (run start/finish/
  erreurs) pour que le dashboard affiche les logs sans exposer le socket
  Docker (choix délibéré : pas de docker.sock monté, accès DB only)

migration 006_admin_dashboard.sql : admin_users, admin_login_attempts,
admin_audit_log, crawl_log

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 22:15:13 +02:00