Hiérarchie
Tenant (Institution)
└── Course (titre, slug, status, theme, publishAt, aiTutorEnabled)
└── CourseModule (titre, sortOrder)
└── CourseLesson (type, content JSON, sortOrder, isPublished, publishAt)
├── CourseLessonMedia (N:N avec MediaAsset)
└── LessonCompletion (1:N par studentId)
CRUD
Cours
POST /api/institution/courses Créer
GET /api/institution/courses Lister
GET /api/institution/courses/[id] Détail
PATCH /api/institution/courses/[id] Mettre à jour
DELETE /api/institution/courses/[id] Supprimer (soft, status=archived)
POST /api/institution/courses/[id]/clone Cloner (deep copy)
POST /api/institution/courses/import Importer IMS CC
Scopes : courses:read (GET) | courses:write (POST/PATCH/DELETE).
Modules
POST /api/institution/courses/[id]/modules Créer
PATCH /api/institution/courses/[id]/modules/[mid]
DELETE /api/institution/courses/[id]/modules/[mid]
PATCH /api/institution/courses/[id]/modules/reorder (bulk sortOrder)
Leçons
POST /api/institution/courses/[id]/modules/[mid]/lessons Créer
PATCH /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
DELETE /api/institution/courses/[id]/modules/[mid]/lessons/[lid]
POST /api/institution/courses/[id]/lessons/reorder (bulk)
Statut du cours
Course.status: draft | published | archived
- draft : non visible pour les étudiants, modifiable
- published : visible pour les inscrits (en tenant compte de publishAt)
- archived : non visible, lecture seule pour l'historique
Course.publishAt planifie la publication automatique via cron.
Publication échelonnée
Combinez Course.publishAt avec Lesson.publishAt pour un contrôle granulaire :
Cours publié le 2026-06-01
Module 1 (sans publishAt) → disponible depuis le 2026-06-01
Leçon 1.1 (sans publishAt) → disponible depuis le 2026-06-01
Leçon 1.2 (publishAt: 2026-06-08) → disponible à partir du 2026-06-08
Module 2 (sans publishAt)
Leçon 2.1 (publishAt: 2026-06-15) → disponible à partir du 2026-06-15
checkLessonAvailability() dans apps/web/lib/lesson-availability.ts valide sur TOUS les endpoints de leçon (view, quiz/start, quiz/submit, complete, interactive).
Prérequis entre leçons
Lesson.prerequisiteLessonId pointe vers la leçon qui DOIT être terminée avant d'accéder à la suivante.
checkLessonAvailability(lesson, { checkPrerequisite: true }) valide :
lesson.isPublished === truelesson.publishAt <= now- Si
prerequisiteLessonId: existence d'unLessonCompletionde l'étudiant pour cette leçon
Clone de cours
POST /api/institution/courses/[id]/clone effectue :
- Crée un nouveau Course avec le slug
{slug}-clone-{n} - Pour chaque Module original → crée une copie
- Pour chaque Lesson originale → crée une copie avec le même content JSON
- NE copie PAS : Enrollments, LessonCompletions, ClassGroups, QuizAttempts, ingestion RAG
- Respecte la limite de cours du plan (checkTenantResourceLimit)
Import IMS Common Cartridge
POST /api/institution/courses/import body multipart/form-data avec .imscc :
- Parseur :
packages/core/src/content/imscc-parser.ts(parseur XML basé sur cheerio) - Prise en charge : IMS CC v1.0, v1.1, v1.2, v1.3
- Ressources prises en charge : web links, file resources, basic LTI links, quizzes QTI 1.2 (mappés vers le Quiz Engine), web content resources
- NON pris en charge : packages de contenu H5P, SCORM 2004 PIF, IMS CP
Ingestion RAG
Course.autoSyncRag: Boolean contrôle la réingestion automatique. Détails dans RAG Ingestion.
Limitations
- Maximum 100 modules par cours (soft limit, performance)
- Maximum 200 leçons par module (soft limit, UX)
- Content JSON par leçon jusqu'à 5 Mo (validé à la sauvegarde)
- Le slug doit être unique par tenant + langue