/** * Appels API du domaine `production`. * * Toutes les requêtes passent par `apiFetch` (Règle J / Règle F). * - `POST /simulations` : timeout 5s (défaut), retry désactivé (POST non-idempotent). * - `GET /simulations/:id` : timeout 5s, retry activé (GET idempotent). * * Erreurs notables : `QUOTA_REACHED` (Free 5/5), `PLAN_INSUFFICIENT` (exam_mode). */ import { apiFetch } from '@/shared/lib/api-client' import type { CreateSimulationPayload, Production, SimulationState, SimulationsListResponse, SujetData, Tache, } from './types' /** Crée une nouvelle simulation. Endpoint : `POST /simulations` (HTTP 201). */ export function createSimulation(payload: CreateSimulationPayload): Promise { return apiFetch('/simulations', { method: 'POST', body: payload }) } /** Récupère une simulation existante. Endpoint : `GET /simulations/:id`. */ export function getSimulation(id: string): Promise { return apiFetch(`/simulations/${id}`) } /** * Sprint 3.7 — liste paginée des simulations de l'utilisateur connecté. * Endpoint : `GET /simulations?page=X&limit=Y`. Tri `created_at DESC` côté backend. * Champs lourds exclus (contenu, rapport, exercices, modele) — cf. SimulationListItem. */ export function listSimulations(page: number, limit: number): Promise { const qs = new URLSearchParams({ page: String(page), limit: String(limit) }) return apiFetch(`/simulations?${qs.toString()}`) } /** * FTD-21 — récupère l'état complet d'une simulation (contenu + sujet + rapport). * Utilisé par `SimulationFlowProvider` pour restaurer une session depuis * `localStorage.expria_simulation_id` au mount. * * Si `rapport === null` → simulation en cours, restaurer `/simulation/ee`. * Sinon → simulation terminée, rediriger vers `/rapport/:id`. */ export function getSimulationState(id: string): Promise { return apiFetch(`/simulations/${id}`) } /** * FTD-21 — autosave du contenu (debounce 30 s + beforeunload). * Endpoint : `PATCH /simulations/:id/contenu`. * Ne retourne rien : le client conserve déjà le texte localement. */ export async function autosaveContenu(id: string, contenu: string): Promise { await apiFetch<{ ok: true }>(`/simulations/${id}/contenu`, { method: 'PATCH', body: { contenu }, }) } /** * FTD-21 — persiste un changement de sujet côté backend. * Endpoint : `PATCH /simulations/:id/sujet`. * Appelé depuis `SimulationFlowProvider.changeSubject` quand l'utilisateur * choisit un autre sujet via `/sujets`. */ export async function updateSujet(id: string, sujetId: string): Promise { await apiFetch<{ sujet: SujetData }>(`/simulations/${id}/sujet`, { method: 'PATCH', body: { sujet_id: sujetId }, }) } /** * Mappe une Tache vers les paramètres de la route `GET /sujets`. * Retourne `null` pour les tâches sans catalogue de sujets côté base * (EO_T1 : sujet fixe connu, EO_T2_LIVE : interaction sans sujet). */ function mapTacheToSujetParams(tache: Tache): { mode: 'EE' | 'EO'; tacheNumber: 1 | 2 | 3 } | null { switch (tache) { case 'EE_T1': return { mode: 'EE', tacheNumber: 1 } case 'EE_T2': return { mode: 'EE', tacheNumber: 2 } case 'EE_T3': return { mode: 'EE', tacheNumber: 3 } case 'EO_T3': return { mode: 'EO', tacheNumber: 3 } case 'EO_T1': return null } } /** * Récupère la liste des sujets actifs disponibles pour une tâche. * Endpoint : `GET /sujets?mode=XX&tache=N`. * * Retourne `[]` immédiatement pour les tâches sans catalogue (EO_T1). */ export function getSujets(tache: Tache): Promise { const params = mapTacheToSujetParams(tache) if (!params) return Promise.resolve([]) const qs = new URLSearchParams({ mode: params.mode, tache: String(params.tacheNumber) }) return apiFetch<{ sujets: SujetData[] }>(`/sujets?${qs.toString()}`).then((r) => r.sujets) }