Commit graph

53 commits

Author SHA1 Message Date
1ae5aa4b57 feat(geocoder): seuil de confiance strict, dedup, purge ancien pipeline
Étape 0 de la refonte pipeline crawl/géocodage.

Auto-requeue du géocodage
- migration 013: trigger trg_bands_geocode_dirty (AFTER UPDATE OF location_text
  ON bands) supprime les band_locations d'un band dès que sa localisation
  change réellement (IS DISTINCT FROM) — plus de points fantômes après un
  déménagement détecté par le crawler incrémental.
- enqueue.py: en plus du déclenchement manuel, un scan automatique tourne
  toutes les ENQUEUE_AUTO_INTERVAL_MIN minutes (défaut 60) et rattrape les
  bands rendus "dirty" par le trigger (idempotent, ON CONFLICT DO NOTHING —
  pas de canal de notification supplémentaire nécessaire). Chaque exécution
  (manuelle ou auto) crée une ligne crawl_run + logs rattachés, en miroir de
  crawler/src/db.py, pour apparaître dans l'activité admin à venir.

Crawl complet en calcul glissant
- remplace CRAWLER_SCHED_FULL_DAY (jour fixe du mois) par
  CRAWLER_FULL_CRAWL_INTERVAL_DAYS (défaut 60) basé sur le checkpoint
  last_full_crawl_at — robuste au calendrier et aux redémarrages (vérifié
  aussi au démarrage, pas seulement le tick quotidien 03:00 UTC).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:07:16 +02:00
88f6f2bff9 fix(geocoder): rejeter la confiance <= seuil (0.7 et moins → LLM)
is_reliable exige strictement > MIN_CONFIDENCE : une confiance de 0.7 pile
(ou moins) n'est plus acceptée et part vers le LLM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:34:42 +02:00
72a52a8d3b feat(geocoder): seuil de confiance strict, dedup, purge ancien pipeline
Durcissement fiabilité (suite audit).

Confiance & granularité
- worker: un résultat n'est accepté ('done') que si confiance >= GEOCODE_MIN_CONFIDENCE
  (0.7) ET granularité non-grossière (rejette country/state/county/region). Sinon
  on continue les fallbacks, puis -> llm_needed.
- fix majeur: sur cache hit la confiance était écrite 0.5 en dur (97% des lignes
  faussées). Elle est désormais lue depuis geocode_cache (recalculée du raw).
- migration 012: colonnes confidence+granularity sur geocode_cache (recalcul des
  entrées Geoapify depuis le raw), geocode_granularity sur band_locations.

Dedup (7x moins de travail)
- fast-path: un band_location dont le (lieu,pays) est déjà 'done' copie le
  résultat sans appel API. Index fonctionnel lower(location_raw).

Ancien pipeline retiré
- geocode_queue n'est plus lu nulle part (endpoints /geocoding/reset-errors et
  /requeue-all supprimés, /live et /geocoding et Monitor basculés sur
  band_locations, panneau admin "ancien pipeline" retiré).

Boutons reset (onglet Géocodage, zone dangereuse)
- POST /admin/api/locations/reset-all: remet tout en queue + efface coords
- POST /admin/api/geocode-cache/purge-nominatim: purge le cache Nominatim

Divers
- groq_worker: coût calculé par modèle (70B vs 8B) au lieu du tarif 70B fixe
- GEOCODE_MIN_CONFIDENCE ajouté aux deux compose

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:32:16 +02:00
2b871584e9 feat(admin): observabilité & provenance du géocodage + fix parser
Objectif: pouvoir debugger la fiabilité des données depuis l'admin.

Parser
- fix split "and": _CITY_SPLIT ne coupe plus sur " and "/"&" (cassait
  "Tyne and Wear", "Bosnia and Herzegovina", "Newcastle upon Tyne"). Garde
  uniquement "/" et "\" (séparateurs canoniques Metal Archives).

Traçabilité LLM
- migration 011: llm_cache reçoit ma_id + location_raw + country (+ index) pour
  relier chaque appel Groq au groupe déclencheur
- groq_worker: renseigne ces colonnes à l'insertion

Admin — lecture par groupe
- GET /admin/api/bands/:ma_id renvoie désormais aussi les band_locations (steps,
  statut, provider, confiance, coords, essais, erreur, query) et les appels LLM
  (modèle, extraction, tokens, coût, prompt/réponse)
- modal band: section "Provenance & géocodage" (crawl MA / géocodage / LLM +
  champs verrouillés manuels)

Admin — stats & debug LLM
- GET /admin/api/geocoding: breakdown par provider (avg confiance) + par modèle
  LLM (dont city=null); nouvelle carte "LLM null"
- nouveau GET /admin/api/llm + onglet "LLM": liste filtrable des appels Groq,
  prompt/réponse dépliables, lien vers le groupe

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 20:10:18 +02:00
39e6b2a66f feat(map): carte multi-localisations — un point par step géocodé
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>
2026-07-02 19:52:40 +02:00
3d3362fea0 feat(i18n): +10 langues (hu, da, fi, sk, hr, sl, lt, lv, et, nb)
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>
2026-07-02 19:47:36 +02:00
f145188d1f fix(geocoder): band_locations.ma_id en BIGINT — débloque 62k bands
bands.ma_id est BIGINT (crawler génère jusqu'à ~3.5e9) mais band_locations.ma_id
avait été créé en INTEGER (max 2.1e9). Tout INSERT pour un band à ma_id élevé
échouait en "integer out of range", erreur avalée silencieusement par l'enqueue.
62 239 bands (sur 96 143 localisables) n'entraient donc jamais dans le pipeline,
d'où 0 llm_needed / 0 erreur trompeurs.

- migration 008: ma_id BIGINT (installs neuves)
- migration 009: ALTER COLUMN ma_id TYPE BIGINT (DB existante)
- enqueue.py: compte les inserts échoués (failed=) au lieu de les avaler
- worker.py: sync_bands_primary prend le lieu d'ORIGINE (step 0) et non la
  dernière localisation — garde les groupes européens en Europe
- admin app.js: coût LLM robuste au NaN (NUMERIC accepte la valeur spéciale NaN)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 19:47:30 +02:00
dba6b9f730 feat(geocoding): boutons UI pour déclencher l'enqueue band_locations
- enqueue.py devient un daemon qui poll job_triggers (type geocoder_enqueue)
  toutes les 15s — plus de commande manuelle à lancer
- API : geocoder_enqueue ajouté aux job types autorisés
- Admin UI page Géocodage : bouton "Lancer l'enqueue" (vert)
- Admin UI Centre de commandes : même bouton dans la section Géocodeur
- docker-compose : geocoder-enqueue passe à restart: unless-stopped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 21:55:40 +02:00
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
3d973c430b fix(ui): topbar z-index 1002 pour que le dropdown langue passe au-dessus de la carte
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 21:13:24 +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
dc9239c38e fix(i18n): sélecteur langue sans emoji flags, détection navigateur multi-lang, défaut anglais
- 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>
2026-07-01 20:51:39 +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
891285a572 fix(crawler): upsert_band_enriched $1→%s (psycopg2 ne supporte pas la syntaxe PG native)
Bug latent : la fonction n'était jamais appelée avant car get_bands_to_enrich
retournait 0. Exposé dès le premier run réel avec la nouvelle priority queue.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:28:03 +02:00
ca45697207 feat(crawler): priority queue + délais réduits (1.5-2.5s) + limit 500
- BAND_MIN_DELAY: 2.5→1.5s, BAND_MAX_DELAY: 5.0→2.5s (avg 2s/band)
- ENRICH_LIMIT: 200→500 (configurable via CRAWLER_ENRICH_LIMIT)
- get_bands_to_enrich: remplace la queue FIFO simple par une file de
  priorité à 4 niveaux :
    1. Nouveaux bands (band_page absent)
    2. Modifiés depuis dernier enrichissement (updated_at > crawled_at + 1min)
    3. Héritage ancien scraper (crawled_at IS NULL, band_page présent)
    4. Stale (crawled_at < now() - 30 days)
- Suppression de get_bands_stale() (logique absorbée par la queue)

Objectif : ~17 jours pour réenrichir les 103k bands
(6000 bands/jour à raison de 500/run × 12 runs/24h)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 21:05:17 +02:00
94d94d926b fix(crawler): corriger le parsing de l'endpoint archive (6 colonnes)
L'endpoint /archives/ajax-band-list retourne 6 colonnes :
  [date, band_link, country_link, genre, time, user]
et non 4 comme supposé initialement. Le band_link est à row[1],
country à row[2], genre à row[3].

Les dates MA n'incluent pas l'année (ex: "June 1") donc date_str=None :
la logique d'arrêt incrémental sur date est désactivée, on re-fetch
les ~800 entrées récentes à chaque run (idempotent, coût négligeable).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:35:53 +02:00
dc242d1390 fix(crawler): FlareSolverr persistent sessions + DB data field + scraper schema
- flaresolverr.py: refactor stateless (create_session/destroy_session/get)
  L'ancienne approche warmup→cookies→requests était bloquée par Cloudflare
  (JA3 TLS fingerprinting). Toutes les requêtes passent maintenant via une
  session Chrome persistante FlareSolverr.

- ma_http.py: MASession utilise les sessions FS persistantes. Ajout de
  _extract_json (html.unescape + extraction <pre>) et _build_url.
  Gestion du refresh sur 403/429 avec recréation de session.

- db.py: ajout du champ data JSONB dans upsert_bands INSERT+ON CONFLICT.
  Avant ce fix, l'URL n'était jamais stockée → get_bands_to_enrich retournait
  toujours 0 résultats → enrichissement mort.

- scraper_band.py: schéma band_page aligné sur l'historique DB (lineup
  structuré avec URLs artistes, discography_url, lyrical_themes, location,
  info_raw). Ajout de "Themes" dans _pick pour couvrir les deux variantes
  de clé HTML (MA utilise parfois "Themes", parfois "Lyrical themes").

- 005_fix_checkpoints_and_data.sql: renomme last_additions_check →
  last_created_check (clé morte vs clé utilisée par le code). Backfill
  enriched=true + colonnes top-level (formed_year, themes, status,
  location_text) depuis band_page JSONB pour les 85k bands de l'ancien scraper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-30 20:29:33 +02:00
3230ba8d9a fix(network): remettre coolify external network + compose_parsing_version=5
Root cause identifié: compose_parsing_version=1 (DEV) vs 5 (PROD).
La v1 stripe coolify: external: true → crée uuid_coolify au lieu du vrai réseau.
La v5 préserve correctement l'external network declaration.

Fix appliqué:
- DB Coolify: compose_parsing_version mis à 5 pour l'app DEV
- Tous services sur networks: [coolify] explicitement
- Section networks: coolify external: true, name: coolify dans les deux compose files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 16:44:09 +02:00
777d95fc79 refactor: supprimer toutes les déclarations réseau custom des compose files
Coolify gère le réseau Docker de manière autonome :
- Crée un réseau projet UUID pour la communication inter-services
- Connecte automatiquement les services avec labels Traefik (même enable=false)
  au vrai réseau coolify via docker network connect après le démarrage

Déclarer coolify: external: true dans le compose causait un bug Coolify 4.1.2
qui strippait la config en coolify: null → créait aec2c803..._coolify au lieu
du vrai réseau coolify.

Suppression complète de bm_internal et coolify des deux compose files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 16:35:54 +02:00
70dd610aa2 fix(migrate): créer un nouveau pg.Client à chaque tentative de retry
pg.Client ne peut pas être réutilisé après un connect() échoué.
Le retry créait le même client → "Client has already been connected"
dès l'attempt 2. On crée maintenant un nouveau client à chaque essai.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 16:16:28 +02:00
c55366fb25 fix: migrate.js retry DB connection + align CORS_ORIGINS pattern on dev
migrate.js now retries up to 12 times with linear backoff (max 30s per attempt)
instead of crashing immediately — prevents container restart loops when the
standalone Coolify DB is temporarily unreachable at startup.

CORS_ORIGINS in docker-compose.dev.yml now uses ${CORS_ORIGINS:-https://dev.metalfrom.eu}
to match the prod pattern and allow Coolify UI override.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 16:12:10 +02:00
bf03647de6 fix(dev): revenir au standalone Coolify DB + traefik.enable=false
Coolify ne connecte au vrai reseau coolify que les services avec
labels Traefik. Fix: ajouter traefik.enable=false sur les services
sans exposition HTTP (crawler, geocoder-worker, flaresolverr) pour
forcer Coolify a les mettre sur le bon reseau.

DATABASE_URL revient a pointer vers la DB standalone Coolify.
Supprime la DB dans le compose (bm_dev_db_data) - retour a
l architecture standalone correcte.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 16:00:03 +02:00
bc06910708 fix(dev): DB dans le compose (plus de dependance reseau Coolify)
Coolify prefixe les reseaux externes avec l UUID du projet, donc
external: true, name: coolify ne fonctionne pas pour les services
sans labels Traefik. Solution : integrer la DB directement dans
le compose sur bm_internal.

- Service db: postgis/postgis:16-3.4, volume bm_dev_db_data
- Tous les DATABASE_URL passent par postgres://bm:@db:5432/bm
- depends_on db (condition: service_healthy) sur tous les consumers
- crawler, geocoder, flaresolverr: plus de reseau coolify (inutile)
- La DB Coolify standalone n9dttfjalj5trqsg3o8twduo peut etre arretee

Env vars requises dans Coolify UI: POSTGRES_PASSWORD, BM_IMPORT_TOKEN,
PGADMIN_EMAIL, PGADMIN_PASSWORD, NOMINATIM_EMAIL, NOMINATIM_USER_AGENT

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:34:25 +02:00
ac7004ca4c fix(dev): ajouter reseau coolify au crawler et geocoder-worker
Sans ce reseau, les containers ne peuvent pas resoudre le hostname
de la DB Coolify (n9dttfjalj5trqsg3o8twduo).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:26:08 +02:00
ab2fee2f43 fix(crawler): supprimer chemin Windows du docstring (SyntaxError \U)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:17:39 +02:00
260200e251 feat(crawler): nouveau service crawler server-side via FlareSolverr
- apps/crawler/ : service Python complet, remplace les scripts locaux
  - FlareSolverr pour bypasser Cloudflare (cookies CF → session requests)
  - Crawl incrémental : /archives/band-list/by/created et /by/modified
  - Crawl complet Europe : pagination AJAX /browse/ajax-country/
  - Enrichissement : pages individuelles de bands (themes, membres, label, hash)
  - Écriture directe en DB (upserts bulk, idempotents)
  - Scheduler intégré (schedule library) : incrémental 4h, enrich 2h, full le 1er du mois
  - Tracking via crawl_run et crawl_checkpoint (migration 004)
- docker-compose.dev.yml : flaresolverr + crawler ajoutés
  Full crawl désactivé en dev (CRAWLER_SCHED_FULL_DAY=0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:15:29 +02:00
d2fcafc6be feat(db): système de migrations SQL + refonte schéma
- apps/api/migrations/ : 4 migrations numérotées idempotentes
  001 : schéma initial (bands, trigger geom, indexes)
  002 : colonnes manquantes (enriched, themes, geocode_*) + fix trigger
        → updated_at mis à jour sur toute UPDATE (pas seulement lat/lon)
  003 : geocode_queue et geocode_cache (formalisées)
  004 : tracking crawl (crawled_at, crawled_hash, ma_created_at,
        ma_modified_at, first_seen_at) + crawl_run + crawl_checkpoint
- apps/api/src/migrate.js : runner qui s applique au démarrage de l api
- apps/api/Dockerfile : CMD lance migrate.js avant server.js
- infra/init.sql : remplacé par notice de redirection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:09:35 +02:00
91166668a6 fix(dev): stabiliser les workers et corriger les domaines
- Supprime le service worker (Playwright, sera réécrit)
- geocoder-worker: restart: no pour éviter la boucle de crash
- Domaines dev-api et dev-pgadmin migrés vers metalfrom.eu
- API_BASE aligné sur dev-api.metalfrom.eu

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 15:04:52 +02:00
d3cb6e64e7 fix(dev): change dev API domain to dev-api.metalfrom.eu
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 14:39:49 +02:00
781382dc65 feat: API_BASE configurable par build arg + DB dev
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 14:30:08 +02:00
9f6fea4492 test deploy 4 2026-06-27 14:05:50 +02:00
89b1a60502 test deploy 3 2026-06-27 14:04:09 +02:00
80dfa7495d test deploy 2 2026-06-27 13:42:35 +02:00
16927f0dd2 test deploy 2026-06-27 13:33:13 +02:00
6c00ee2251 fix: docker-compose.dev.yml standalone (not overlay) pour Coolify
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:52:11 +02:00
af84da84da fix: revenir aux labels Traefik hardcodés + docker-compose.dev.yml
Coolify ne développe pas \${VAR:-default} dans les labels Docker.
Labels prod: bm-api, bm-web, bm-pgadmin (inchangés).
docker-compose.dev.yml: override pour l'env dev avec les labels
dev-api, dev-web, dev-pgadmin et CORS_ORIGINS dev.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:49:28 +02:00
d08584704f refactor: domaines et router Traefik configurables par env vars
APP_ENV, DOMAIN_WEB, DOMAIN_WEB_ALT, DOMAIN_API, DOMAIN_PGADMIN
permettent de déployer prod et dev sur le même Traefik sans conflit.
CORS_ORIGINS aussi injecté directement dans le service api.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:38:52 +02:00
06a5bf2ef8 fix: geocoder/worker network + CORS configurable par env
- Ajoute geocoder-worker et worker sur le réseau coolify pour
  résoudre le hostname DB (lgrep99...) qui n'est accessible que
  depuis le réseau coolify
- CORS_ORIGINS env var optionnel pour configurer les origines
  autorisées (utilisé par l'env dev)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:37:09 +02:00
d1a6a6bc27 fix: geocoder/worker network + CORS configurable par env
- Ajoute geocoder-worker et worker sur le réseau coolify pour
  résoudre le hostname DB (lgrep99...) qui n'est accessible que
  depuis le réseau coolify
- CORS_ORIGINS env var optionnel pour configurer les origines
  autorisées (utilisé par l'env dev)
2026-06-27 10:34:05 +00:00
2b26382343 test: validation webhook auto-deploy 2026-06-27 11:43:02 +02:00
eafd771bcc test: webhook auto-deploy Coolify via Forgejo 2026-06-27 11:38:07 +02:00
58561ef79f fix: exclure .claude du tracking git 2026-06-27 11:35:14 +02:00
d241d168e3 feat: integrer override api en production (supprimer mecanisme override) 2026-06-27 11:35:06 +02:00
68dfdf10c2 refactor: deplacer docker-compose.yml a la racine pour compatibilite Coolify 2026-06-27 11:32:51 +02:00
97248a13e7 fix: retirer depends_on db (service supprime) 2026-06-27 11:30:32 +02:00