Aller au contenu

TrainHub — Vue d'ensemble

Description

TrainHub (nom de code local : c-training) est une plateforme d'entraînement quotidien au langage C, développée pour les étudiants du C-Pool EPITECH (initialement le campus d'Abidjan). Elle propose des sessions chronométrées avec plusieurs types de questions, des exercices de code notés automatiquement, et du contenu pédagogique (guides de projets, tutoriels) rédigé en pseudocode + Mermaid plutôt qu'en code C, pour ne pas donner de solutions toutes faites.

  • Repository : EpitechAfrik/trainhub
  • Origine : développé et versionné sur le repo personnel YemalinModeste/c-training-abj, transmis à l'org EPITECH lors du départ du développeur.
  • Hébergement : Vercel (projet daily-c-language-training-platform)
  • URL production : https://trainhub.vercel.app par défaut dans le code (app/layout.tsx, app/sitemap.ts) — à confirmer sur le dashboard Vercel, un domaine personnalisé a pu être configuré depuis.

Fonctionnalités principales

Côté étudiant (app/etudiant/)

  • Entraînement libre et sessions planifiées (créées par un admin, ciblées sur un étudiant ou un groupe), avec statistiques par session et globales.
  • Types de questions : QCM (qcm), prédiction de sortie (code_output), détection d'erreur (error_detection), texte à trous (fill_blank).
  • Éditeur Monaco intégré pour les questions à code.
  • Recode : exercices de code notés automatiquement (compilation + tests cachés via Judge0/Piston). Soumission automatique en cas de fermeture/déconnexion (navigator.sendBeacon → /api/recode/auto-submit) pour ne pas perdre le travail en cas de timer expiré.
  • Contenu pédagogique : blog de tutoriels C (pointeurs, varargs, listes chaînées...), guides par projet Epitech (CPE, PSU, MUL, MATH — ex. Setting Up, my_ls, my_sudo, 101pong, my_hunter), diagnostic de lacunes, vocabulaire, planning. Convention du projet : jamais de code C dans les guides de projets — pseudocode en français + diagrammes Mermaid + visualisations React, pour forcer la réflexion plutôt que le copier-coller.

Côté admin (app/admin/)

  • Gestion des questions et catégories, avec prévisualisation/test d'une question avant publication.
  • Planification de sessions (ciblage étudiant ou groupe), gestion des groupes d'étudiants et de leurs membres.
  • Import CSV d'étudiants, reset de mot de passe, révocation de comptes.
  • Gestion des droits (rôles admin / super_admin).
  • Gestion des exercices Recode et de leurs soumissions.
  • Dashboard de suivi d'activité (engagement, temps passé par page/section).

Cron

Un seul cron Vercel configuré (vercel.json), tous les jours à 7h : GET/POST /api/cron/training-reminders. Il liste les sessions planifiées publiées du jour (ciblage direct ou via groupe) non complétées, et envoie un rappel par email via Resend (par lot, avec délai pour respecter le rate limit). Protégé par CRON_SECRET — fail-closed : sans ce secret configuré, la route échoue volontairement plutôt que de tourner sans protection.

Architecture

Stack technique

Composant Technologie
Framework Next.js 16 (App Router)
UI React 19, TypeScript, Tailwind CSS 4, Shadcn UI
Éditeur de code Monaco (@monaco-editor/react, chargé en dynamique client-only)
Auth + Base de données Supabase (PostgreSQL + Auth) — seule couche de données active
Exécution de code Judge0 (auto-hébergé, judge0/docker-compose.yml) par défaut, Piston en alternative
Emails Resend
Hébergement Vercel (build + cron)
Middleware proxy.ts (convention Next 16 — remplace middleware.ts), rafraîchit la session Supabase et bloque /auth/signup*

Tentative de migration self-managed — abandonnée

Une tentative de migration vers un backend Postgres auto-hébergé + Drizzle ORM a été menée sur une branche (migration/self-managed, 4 commits), puis entièrement annulée (commit revert: abandon de la migration self-managed — retour à Supabase). Les branches main et development — celles poussées vers l'org EPITECH — n'ont jamais contenu cette tentative. Décision produit actuelle : rester 100% Supabase. Des résidus subsistent dans package.json (pg, @types/pg, tsx en devDependencies), utilisés uniquement par un script standalone d'import de questions (scripts/import_questions.ts), pas par le runtime de l'app.

Exécution de code (Judge0 / Piston)

Judge0 déprécié — repris par devkora (Zeno)

Tel que trouvé dans le code au moment de cette documentation, lib/compiler/index.ts sélectionne le backend via COMPILER_BACKEND (judge0 par défaut ou piston). Cette approche Judge0/Piston auto-hébergé est dépréciée : la sandbox d'exécution de code est reprise par Edwin sous forme d'un module nommé devkora, côté projet Zeno. Se référer à Edwin / au repo Zeno pour l'implémentation à jour plutôt qu'à lib/compiler/judge0.ts ci-dessous, qui documente l'ancien fonctionnement.

  • Judge0 (lib/compiler/judge0.ts) : soumission asynchrone + polling (30 tentatives max, 1s d'intervalle). URL par défaut http://localhost:2358 (instance auto-hébergée, voir judge0/docker-compose.yml — Redis + Postgres + workers, auth par token AUTHN_TOKEN). Choix automatique du compilateur : Clang (language_id=75, défaut) ou GCC (language_id=50, si les flags de compilation contiennent -std=gnu).
  • Piston (lib/compiler/piston.ts) : alternative configurée via PISTON_API_URL.

Modèle de données (Supabase / PostgreSQL)

Source de vérité : scripts/000_full_schema.sql + migrations numérotées (012 à 018) + tracking_migration.sql, appliquées manuellement sur le projet Supabase (pas de système de migration automatisé type Supabase CLI ou Drizzle en prod).

Table Rôle
profiles Comptes utilisateurs (étend auth.users), rôle student / admin / super_admin
categories Catégories de questions (Variables, Boucles, Pointeurs...)
questions Banque de questions (type, difficulté, snippet de code, options, réponse correcte)
sessions Session d'entraînement libre d'un étudiant
session_questions Questions liées à une session (avec ordre)
attempts Réponses/scores par session
question_mastery Suivi de maîtrise par question/utilisateur
planned_sessions Session planifiée par un admin (titre, date, statut de publication)
planned_session_questions Questions d'une session planifiée
planned_session_targets Ciblage d'une session planifiée (étudiant ou groupe)
planned_session_attempts Tentative d'un étudiant sur une session planifiée
planned_session_answers Réponses détaillées d'une tentative planifiée
student_groups / student_group_members Groupes d'étudiants et appartenance
recode_exercises Exercices de code notés (Recode)
recode_exercise_targets Ciblage des exercices Recode
recode_submissions Soumissions de code + résultats de tests
student_activity Tracking d'engagement (page_enter/page_leave/section_enter/section_leave, durée) — RLS : un étudiant écrit ses propres événements, admin/super_admin lisent/suppriment tout

Autres migrations notables : 013_roles_and_classes.sql (rôles étendus), 014_microsoft_auth.sql (auth Microsoft/Azure), 016-018 (toggles et fonctions du mode Recode).

Routes API (app/api/**)

Route Description
POST /api/compile Compile/exécute du code C via Judge0 ou Piston ; vérifie header Epitech, fonctions bannies, et applique un linter de coding style Epitech maison ; auth Supabase requise
POST /api/recode/auto-submit Soumission serveur-side (compile + tests + sauvegarde recode_submissions) pour timer expiré ou fermeture de page
GET/POST /api/cron/training-reminders Cron quotidien (protégé CRON_SECRET), rappels d'entraînement par email
POST /api/admin/invite Invite un admin/super_admin (crée le compte + envoie les identifiants)
POST /api/admin/import-students Import en masse d'étudiants (CSV/JSON) + envoi des identifiants
POST /api/admin/groups/import-csv Import CSV de groupes d'étudiants
POST /api/admin/notify-session Notifie par email les cibles d'une session planifiée
POST /api/admin/reset-password Réinitialise le mot de passe d'un étudiant (admin)
POST /api/admin/revoke Révoque un compte admin (super_admin uniquement)
DELETE /api/admin/students/[id] Supprime un compte étudiant
POST /api/auth/forgot-password Mot de passe oublié (self-service étudiant)

Toutes les routes utilisant la clé de service (SUPABASE_SERVICE_ROLE_KEY) instancient un client Supabase admin dédié (autoRefreshToken: false, persistSession: false) et vérifient le rôle de l'appelant avant d'agir.

Variables d'environnement

Aucun .env.example n'est présent dans le repo — variables inférées du code source :

Variable Rôle
NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY Client Supabase (browser/SSR)
SUPABASE_SERVICE_ROLE_KEY Client admin Supabase (routes API privilégiées, cron)
NEXT_PUBLIC_APP_URL Base URL pour les liens dans les emails et les redirects d'auth (défaut : https://trainhub.vercel.app)
NEXT_PUBLIC_DEV_SUPABASE_REDIRECT_URL Override du redirect d'auth en dev
CRON_SECRET Secret Bearer pour authentifier l'appel cron Vercel — obligatoire (fail-closed sans lui)
RESEND_API_KEY, RESEND_FROM_EMAIL Envoi d'emails (défaut expéditeur : TrainHub <noreply@trainhub.epitech.eu>)
COMPILER_BACKEND judge0 (défaut) ou piston
JUDGE0_API_URL (défaut http://localhost:2358), JUDGE0_API_KEY Sandbox Judge0
PISTON_API_URL Si backend Piston choisi
POSTGRES_URL_NON_POOLING, NODE_TLS_REJECT_UNAUTHORIZED Utilisés uniquement par le script standalone scripts/import_questions.ts (connexion Postgres directe pour import de questions) — pas par l'app runtime

Déploiement

  • Scripts standards Next.js : pnpm dev, pnpm build (next build), pnpm lint, pnpm start.
  • Déployé sur Vercel (projet lié via .vercel/project.json — toujours vérifier ce fichier ou demander confirmation avant tout déploiement, cf. AGENTS.md du projet).
  • vercel.json ne configure que le cron training-reminders (7h quotidien).
  • Aucun Dockerfile ni CI applicatif dans le repo (le docker-compose.yml de la tentative self-managed a été supprimé au revert). Seul judge0/docker-compose.yml reste, pour l'instance Judge0 auto-hébergée — à héberger séparément de l'app (VPS, conteneur dédié) et pointer via JUDGE0_API_URL en prod.
  • Migrations SQL (scripts/*.sql) à appliquer manuellement, dans l'ordre numérique, sur le projet Supabase.

Points de vigilance connus

  • next.config.mjs a typescript.ignoreBuildErrors: true — les erreurs TypeScript ne bloquent pas le build Vercel, une régression de types peut passer inaperçue jusqu'en prod.
  • JUDGE0_API_URL doit être explicitement configuré en prod (le défaut localhost:2358 échouera silencieusement en timeout sur /api/compile et /api/recode/auto-submit) — sauf si la bascule vers devkora (Zeno, repris par Edwin) est déjà en place, auquel cas ce point ne s'applique plus.
  • Si des artefacts lib/db/, drizzle.config.ts ou infra/ réapparaissent sur une branche, c'est probablement une reprise accidentelle de la tentative de migration abandonnée — la décision produit est de rester 100% Supabase.

Détails et procédures : Erreurs courantes, Diagnostics, Runbook.