Skip to main content

Architecture technique

CEL est une application full-stack pour gérer des ligues d’esport autour d’EA Sports FC.

Vue d’ensemble

  • web/ : frontend Next.js 16 / React 19.
  • convex/ : backend Convex self-hosted.
  • Scripts racine : build, tests Convex, stack self-hosted, backups/restores, Infisical.

Qualité statique

Depuis la racine, npm run lint contrôle à la fois le backend Convex et web/src. Les deux typechecks restent explicites car ils utilisent des projets TypeScript distincts : npm run typecheck:convex puis cd web && npm run typecheck. npx knip analyse séparément les workspaces racine et web; son entrée web/design-system.entry.tsx protège les primitives réellement exposées à /design-sync même lorsqu’elles ne sont pas importées par l’application.

Stack vérifiée

  • Next.js 16
  • React 19
  • TypeScript
  • TailwindCSS v4
  • Shadcn UI / Radix UI
  • TanStack Table
  • React Hook Form + Zod
  • Convex React
  • TipTap
  • Algolia / React InstantSearch
  • PostHog
  • Framer Motion
  • Recharts
  • React Three Fiber / Drei

CI/CD réel et séparation des responsabilités

Workflows actifs

Règle d’architecture CI/CD

  • pipeline.yml = qualité et conformité d’intégration, pas de déploiement. Le job lint-convex exécute test:environment-contract puis env:check ainsi que les gates d’architecture avant ESLint et le typecheck. Le job GitHub-hosted e2e-web-smoke exécute les scénarios navigateur autonomes sans secret. e2e-web-auth démarre un Convex isolé et vérifie une vraie connexion avec sa redirection. Les deux doivent réussir avant build-web et héritent des trois origines publiques locales définies au niveau du workflow. Les jobs Web qui importent convex/_generated installent les dépendances verrouillées de la racine puis celles de web/ : la résolution des modules part du fichier généré situé dans convex/, pas du seul projet Web.
  • deploy.yml = exécution manuelle de production.
  • Pendant le build de l’image web, l’upload des source maps PostHog est best-effort : une erreur réseau PostHog ne bloque pas le déploiement, et les .map client sont supprimées avant l’image runtime.
  • release.yml = versioning uniquement, sans déclenchement du déploiement.
  • docs.yml = validation documentation, sans impact applicatif.

Frontend web/

Les hooks de production utilisent directement convex/react pour les données réactives. Ils n’exposent pas de compatibilité de cache factice (query-client, useQueryClient, refetch sans effet ou états d’erreur/chargement codés en dur). Le gate npm run test:data-adapter-boundary protège cette frontière dans le pre-push et la CI. Scripts principaux :

Frontière E2E

La suite navigateur possède une seule configuration, web/playwright.config.ts, et une seule racine de specs, web/e2e/. Sans E2E_BASE_URL, Playwright possède le serveur Next local et refuse de réutiliser un processus déjà actif ; cela empêche de valider par erreur le checkout d’un autre worktree. E2E_BASE_URL active explicitement le mode serveur externe. Les tags séparent les niveaux de prérequis : @smoke ne demande ni secret ni backend alimenté, @auth demande un compte de test, @live-data demande des données backend et @write annonce des mutations. Avec E2E_REQUIRE_AUTH_FIXTURE=true, l’absence des identifiants d’authentification est une erreur, jamais un skip silencieux.

Frontière des images distantes

L’optimiseur d’images Next.js accepte uniquement les uploads servis sous /uploads/** par l’origine exacte de NEXT_PUBLIC_CONVEX_AUTH_API_URL, les avatars Google sous lh3.googleusercontent.com/a/** et les drapeaux de flagcdn.com. L’origine des uploads est dérivée du contrat d’environnement : aucun port local historique n’est codé en dur. Les redirections distantes sont désactivées et l’accès aux IP locales n’est permis qu’hors production.

Backend convex/

Scripts principaux :
Le backend et le dashboard du Compose Convex local utilisent restart: unless-stopped afin de revenir automatiquement après un redémarrage de Docker ou de WSL, sauf arrêt explicite par l’utilisateur. Les routes HTTP personnalisées sont inventoriées dans api-http.md.

Frontière d’identité

  • convex/lib/actor.ts est le socle neutre de résolution de l’acteur.
  • Une fonction Convex publique dérive toujours l’acteur de ctx.auth; le frontend et les routes HTTP ne lui transmettent ni actorUserId ni requesterUserId.
  • Les champs d’acteur des fonctions internes sont réservés à la causalité des jobs et à la provenance d’audit. Ils ne servent jamais à autoriser une requête publique.
  • Les domaines réexportent les gardes neutres depuis leur _shared.ts afin de ne pas dépendre de l’implémentation d’identité d’un autre domaine.
  • npm run test:identity-boundary empêche la réintroduction d’un identifiant d’acteur fourni par un appelant public.

Frontières des domaines Convex

Les primitives utilisées par plusieurs domaines vivent dans convex/lib/ : la timezone applicative, la construction du texte d’audit, les recherches de clubs/profils et les contrôles d’accès aux clubs. admin, competition et social peuvent conserver des intégrations métier explicites (annonces, notifications, traitements planifiés), mais ne récupèrent pas ces primitives depuis le _shared.ts ou l’adapter d’un autre domaine. npm run test:domain-boundary résout canoniquement chaque import TypeScript de production et refuse par défaut toute dépendance entre ces trois domaines. Son allowlist nomme précisément les fichiers, modules et symboles des intégrations métier actuelles ; un nouveau helper reste donc interdit même s’il est ajouté à un module déjà autorisé. Le gate scanne aussi convex/lib/ et lui interdit sans exception d’importer admin, competition ou social, afin que le socle neutre ne puisse pas servir de bridge entre domaines.

Secrets et environment

config/environment-contract.mjs est la source déclarative des profils, des consommateurs, de la visibilité et des relations entre variables. Les gardes suivantes couvrent le code Convex/Web, les Dockerfiles, les Compose et les fichiers d’exemple :
Le repo fournit aussi les scripts Infisical :
convex:env:sync valide le profil local puis pousse exactement les clés déclarées pour convex-runtime. Les listes ad hoc et le scan de process.env.* ne participent plus à la synchronisation.

Production : composants réels

Le stack de production défini dans docker-compose.prod.yml contient :
  • postgres
  • pgbouncer
  • postgres-backup
  • convex-backend
  • convex-dashboard
  • web
  • cloudflared
  • portainer
Invariants connus :
  • frontend canonique : https://cel-eleague.com
  • Convex auth HTTP : https://api.cel-eleague.com/api
  • déploiement sur main/master dans le workflow manuel
  • runner de déploiement : self-hosted macOS dans deploy.yml

Crons et jobs actifs (Convex)

Enregistrement piloté par convex/admin/crons.ts (initCrons) :
  • match-auto-validation0 0 * * *
  • match-ea-score-sync*/30 20-23 * * 1-5
  • person-sanction-lifecycle-maintenance*/15 * * * *
  • transfer-lifecycle-maintenance*/15 * * * *
  • stripe-premium-entitlements-reconcile0 2 * * *
initCrons retire explicitement les IDs suivants :
  • user-ban-expiration-cleanup
  • totw-weekly
Autres points opérables via API admin Convex :
  • listCronConfigs
  • listCronExecutions
  • upsertCronConfig
  • triggerCron

Backups et restauration

Backups Convex :
Restauration Convex :
Comportements observés :
  • snapshots dans backups/convex/<timestamp>/convex-export.zip
  • symlink backups/convex/latest
  • convex:restore = remplacement par défaut (--replace-all)
  • --append à n’utiliser que si lineage/tableaux compatibles

Développement local

Prérequis :
  • Node.js 22+
  • npm
  • PostgreSQL 16+ ou Docker pour le développement ; la production utilise PostgreSQL 18
Commandes de montée en charge :
Frontend local :
La première commande préserve un éventuel fichier .env existant. Le postinstall crée web/.env.local comme lien vers ce fichier racine. Il ne faut pas copier un exemple vers ce lien, car la copie écraserait .env. Frontend via Portless : https://cel.localhost. Les deux surfaces Convex browser-facing sont https://cel-convex.localhost et https://cel-convex-site.localhost/api. Profil direct explicite : npm run dev:port puis http://localhost:3000, avec NEXT_PUBLIC_APP_URL et les deux URLs Convex configurées sur les ports hôte de .env.convex.

Écarts opératoires restant à traiter

  • Les scripts healthcheck.sh, monitor-prod.sh et monitor-prod-rich.sh contiennent encore des références à une ancienne stack de monitoring absente du compose de production.
  • Certains chemins de restauration et noms de conteneurs historiques doivent être revérifiés avant exécution.
Validation runtime externe requise :
  • Les domaines exacts exposés sur le tunnel/public et le schéma d’alerting externe ne sont pas testés dans une session d’exécution ici.
Last modified on August 9, 2026