metalfrom.eu/docker-compose.dev.yml
Nicolas Fryder 43d6527172
Some checks are pending
CI / javascript (push) Waiting to run
CI / python (push) Waiting to run
CI / mutation (push) Waiting to run
fix: sync du point principal + verrou de migration, redis mort retiré, tests par propriétés
Passe critique sur les zones non vérifiées.

BUG — la correction manuelle d'un lieu n'atteignait pas la moitié du site
bands.lat/lon/geom est une dénormalisation du lieu d'origine, maintenue par
sync_bands_primary() dans geocoder/worker.py. La saisie manuelle de
coordonnées (PATCH /admin/api/locations/:id, ajoutée récemment) écrivait
band_locations sans jamais la déclencher : la correction apparaissait sur la
carte — qui lit band_locations — mais jamais dans la liste, les statistiques
ni la heatmap, qui lisent bands. Le groupe restait affiché au mauvais endroit
indéfiniment.
La synchronisation est répliquée en SQL dans la même transaction, avec la même
règle de tri (step_order ASC, id ASC) que le worker Python.

BUG — course entre réplicas au démarrage (migrate.js)
Le script s'exécute au démarrage de CHAQUE conteneur API. Deux réplicas
démarrant ensemble lisaient tous deux schema_migrations vide et appliquaient
les mêmes fichiers en parallèle : au mieux une violation de clé primaire qui
faisait échouer le démarrage, au pire deux ALTER concurrents.
Verrou consultatif pg_advisory_lock, relâché explicitement. migrate.js
n'exécute plus au chargement s'il est importé (nécessaire pour le tester).

CODE MORT — redis
Signalé au tout début, jamais retiré : un conteneur redis + un volume
persistant dans les DEUX composes, sans une seule référence dans le code.

TESTS AJOUTÉS

- Intégration migrations (7 tests) : application sur base vierge, rejouabilité,
  ordre lexicographique, relâchement du verrou, échec bruyant sur migration
  invalide, et surtout DEUX MIGRATIONS SIMULTANÉES sur une base vierge —
  le cas qui motivait le verrou.

- Tests par PROPRIÉTÉS (fast-check, 21 tests) : batterie qui manquait.
  Les tests par l'exemple ne couvrent que les cas auxquels on a pensé.
  L'invariant central : toute entrée arbitraire produit soit une valeur
  normalisée valide, soit une ValidationError — jamais une autre exception,
  jamais NaN. C'est ce qui garantit un 400 plutôt qu'un 500. Vérifie aussi
  la cohérence offset = (page-1) × pageSize, le domaine des coordonnées,
  et qu'aucun caractère de contrôle ne survit à la validation.

Tests : 601 JS + 61 intégration, 63 Python, 89 Playwright

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-18 21:03:17 +02:00

162 lines
4.4 KiB
YAML

services:
flaresolverr:
image: ghcr.io/flaresolverr/flaresolverr:latest
environment:
LOG_LEVEL: info
CAPTCHA_SOLVER: none
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
crawler:
build:
context: apps/crawler
environment:
DATABASE_URL: ${DATABASE_URL}
FLARESOLVERR_URL: http://flaresolverr:8191
CRAWLER_SCHED_INCREMENTAL_H: "4"
CRAWLER_SCHED_ENRICH_H: "2"
CRAWLER_FULL_CRAWL_INTERVAL_DAYS: "0"
depends_on:
- flaresolverr
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
geocoder-enqueue:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GEOCODE_ENQUEUE_BATCH: "100000"
GEOCODE_ENQUEUE_POLL: "15"
ENQUEUE_AUTO_INTERVAL_MIN: "60"
command: ["python", "src/enqueue.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
geocoder-worker:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GEOAPIFY_API_KEY: ${GEOAPIFY_API_KEY}
GEOCODER_MIN_DELAY: "0.22"
GEOCODER_JITTER: "0.10"
GEOCODE_MAX_PER_RUN: "100000"
GEOCODE_MAX_GEO_TRIES: "3"
GEOCODE_MIN_CONFIDENCE: "0.7"
command: ["python", "src/worker.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
groq-worker:
build:
context: apps/geocoder
working_dir: /app
environment:
DATABASE_URL: ${DATABASE_URL}
GROQ_API_KEY: ${GROQ_API_KEY}
GROQ_MAX_TRIES: "5"
GROQ_CALL_DELAY: "2.5"
command: ["python", "src/groq_worker.py"]
networks:
- coolify
labels:
- traefik.enable=false
restart: unless-stopped
pgadmin:
image: dpage/pgadmin4:8
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
PGADMIN_CONFIG_SERVER_MODE: "True"
volumes:
- bm_dev_pgadmin_data:/var/lib/pgadmin
networks:
- coolify
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.dev-pgadmin.rule=Host(`dev-pgadmin.metalfrom.eu`)
- traefik.http.routers.dev-pgadmin.entrypoints=https
- traefik.http.routers.dev-pgadmin.tls=true
- traefik.http.routers.dev-pgadmin.tls.certresolver=letsencrypt
- traefik.http.services.dev-pgadmin.loadbalancer.server.port=80
api:
build:
context: apps/api
environment:
PORT: "3000"
DATABASE_URL: ${DATABASE_URL}
BM_IMPORT_TOKEN: ${BM_IMPORT_TOKEN}
CORS_ORIGINS: ${CORS_ORIGINS:-https://dev.metalfrom.eu}
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET}
ADMIN_SEED_USERNAME: ${ADMIN_SEED_USERNAME}
ADMIN_SEED_PASSWORD_HASH: ${ADMIN_SEED_PASSWORD_HASH}
networks:
coolify:
aliases:
- dev-api-internal
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.dev-api.rule=Host(`dev-api.metalfrom.eu`)
- traefik.http.routers.dev-api.entrypoints=https
- traefik.http.routers.dev-api.tls=true
- traefik.http.routers.dev-api.tls.certresolver=letsencrypt
- traefik.http.services.dev-api.loadbalancer.server.port=3000
admin:
build:
context: apps/admin
args:
API_UPSTREAM: http://dev-api-internal:3000
networks:
- coolify
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.dev-admin.rule=Host(`admin.dev.metalfrom.eu`)
- traefik.http.routers.dev-admin.entrypoints=https
- traefik.http.routers.dev-admin.tls=true
- traefik.http.routers.dev-admin.tls.certresolver=letsencrypt
- traefik.http.services.dev-admin.loadbalancer.server.port=80
web:
build:
context: apps/web
args:
API_BASE: https://dev-api.metalfrom.eu
networks:
- coolify
labels:
- traefik.enable=true
- traefik.docker.network=coolify
- traefik.http.routers.dev-web.rule=Host(`dev.metalfrom.eu`)
- traefik.http.routers.dev-web.entrypoints=https
- traefik.http.routers.dev-web.tls=true
- traefik.http.routers.dev-web.tls.certresolver=letsencrypt
- traefik.http.services.dev-web.loadbalancer.server.port=80
networks:
coolify:
external: true
name: coolify
volumes:
bm_dev_pgadmin_data: