API — Structure académique & inscriptions aux cours
Spécification REST pour la gestion des programmes d’études, niveaux, classes, cohortes et inscriptions aux cours (individuelles ou groupées).
Référence conceptuelle : /academic-concepts
DDL d’extension : database/03_academic_structure_api.sql
1. Module NestJS
src/modules/academic-structure/
├── academic-structure.module.ts
├── controllers/
│ ├── programs.controller.ts
│ ├── program-curriculum.controller.ts
│ ├── levels.controller.ts
│ ├── academic-years.controller.ts
│ ├── school-classes.controller.ts
│ ├── cohorts.controller.ts
│ └── departments.controller.ts # lecture seule (listes déroulantes)
├── services/
│ ├── program.service.ts
│ ├── program-curriculum.service.ts
│ ├── level.service.ts
│ ├── academic-year.service.ts
│ ├── school-class.service.ts
│ ├── cohort.service.ts
│ └── course-enrollment.service.ts # port vers pedagogy + email
├── interfaces/ # ports repository
├── repositories/ # TypeORM (cible) puis in-memory (phase 1)
└── dto/
Extension pedagogy : CourseEnrollmentsController sous /api/courses/:courseId/enrollments (service partagé ou import du port CourseEnrollmentService).
Guards globaux : JwtAuthGuard, PermissionsGuard — deny-by-default.
2. Modèle de données (résumé)
department
├── program ──► program_level ──► semester ──► teaching_unit ──► subject
│ (niveaux du cursus) (périodes) (UE) (matières)
├── cohort ──► cohort_member ──► student
└── course (pedagogy) ──► course_participation ──► student
school_class ──► class_student ──► student
│
└── program + level + academic_year
| Table | Rôle |
|---|---|
program |
Formation / cursus (diplôme, filière, durée) |
level |
Catalogue global LMD (Licence 1, Master 1…) |
program_level |
Niveau d’un programme (ordre, ECTS) |
semester |
Période académique d’un niveau |
teaching_unit |
UE |
subject |
Matière (lien optionnel course_id → cours LMS) |
school_class |
Classe (programme + niveau + année + capacité) |
class_student |
Affectation étudiant ↔ classe |
cohort |
Cohorte (programme + année) |
cohort_member |
Affectation étudiant ↔ cohorte |
course_participation |
Inscription étudiant ↔ cours |
course_enrollment_batch |
Journal d’une inscription groupée |
3. Conventions API
| Règle | Valeur |
|---|---|
| Préfixe | /api |
| Auth | Bearer JWT (access-token) |
| Pagination | ?page=1&limit=20 → { items, meta: { page, limit, total } } |
| Tri | ?sort=name&order=asc |
| Recherche | ?q= (nom, code) |
| IDs | UUID v4 |
| Erreurs | I18nHttpException + clés academic.*, enrollment.* |
| Dates | ISO 8601 (2024-09-01 ou 2024-09-01T00:00:00.000Z) |
4. Programmes d’études (/api/programs)
Permission lecture : cursus.view | program.manage
Permission écriture : program.manage | cursus.manage (curriculum)
4.1 Lister les programmes
GET /api/programs
Query : departmentId, status (draft|active|archived), q, page, limit
Réponse 200 :
{
"items": [
{
"id": "uuid",
"code": "MCI",
"name": "Master Commerce international",
"specialty": "Commerce international",
"degreeType": "master",
"durationYears": 2,
"totalEcts": 120,
"status": "active",
"levelCount": 2,
"department": { "id": "uuid", "name": "Commerce" }
}
],
"meta": { "page": 1, "limit": 20, "total": 3 }
}
4.2 Détail programme
GET /api/programs/:programId
Inclut : général, diplôme, compteurs (niveaux, semestres, ECTS).
4.3 Créer un programme (brouillon)
POST /api/programs
Body :
{
"departmentId": "uuid",
"code": "MCI",
"name": "Master Commerce international",
"specialty": "Commerce international",
"description": "Parcours M1–M2…",
"degreeType": "master",
"durationYears": 2,
"totalEcts": 120,
"diplomaTitle": "Master Commerce international"
}
Réponse 201 : objet programme complet (status: "draft").
4.4 Mettre à jour le général
PATCH /api/programs/:programId/general
Body partiel : name, description, specialty, degreeType, durationYears, totalEcts.
4.5 Diplôme
PATCH /api/programs/:programId/diploma
Body : { "diplomaTitle": "Master Commerce international" }
4.6 Statut
PATCH /api/programs/:programId/status
Body : { "status": "active" | "archived" }
Règle : active exige au moins 1 program_level.
4.7 Supprimer
DELETE /api/programs/:programId
409 si classes/cohortes actives liées.
5. Cursus — niveaux, semestres, UE (/api/programs/:programId/curriculum)
Permission : cursus.manage
5.1 Lire le cursus (arbre complet)
GET /api/programs/:programId/curriculum
Réponse :
{
"programId": "uuid",
"levels": [
{
"id": "uuid",
"levelId": "uuid",
"label": "Master 1 (M1)",
"sortOrder": 1,
"ects": 60,
"semesterCount": 2,
"semesters": [
{
"id": "uuid",
"number": 1,
"startDate": "2024-09-01",
"endDate": "2025-01-31",
"ects": 30,
"teachingUnits": [
{
"id": "uuid",
"code": "UE-FCI",
"name": "Fondamentaux du commerce international",
"ects": 30,
"subjectCount": 2,
"subjects": [
{ "id": "uuid", "code": "MAT-101", "name": "Introduction", "hours": 24 }
]
}
]
}
]
}
]
}
5.2 Remplacer le cursus (idempotent)
PUT /api/programs/:programId/curriculum
Body : même structure sans IDs obligatoires (création) ; IDs présents = mise à jour ; niveaux absents = suppression (si pas de dépendances).
Validations :
sortOrderunique par programme ;- somme ECTS semestres ≤ ECTS niveau (avertissement ou erreur configurable) ;
- dates semestre cohérentes.
Réponse 200 : arbre complet après persistance.
6. Niveaux d’étude — catalogue (/api/levels)
Permission lecture : cursus.view
Permission écriture : level.manage
6.1 Lister
GET /api/levels?type=LMD&active=true&q=
Réponse :
{
"items": [
{
"id": "uuid",
"name": "Licence 1",
"type": "LMD",
"sortOrder": 1,
"isActive": true
}
]
}
6.2 Créer / modifier / désactiver
POST /api/levels
PATCH /api/levels/:levelId
PATCH /api/levels/:levelId/status → { "isActive": false }
Body création : { "name", "type": "LMD"|"training", "sortOrder" }
7. Années académiques (/api/academic-years)
Permission lecture : cursus.view
Permission écriture : academic_year.manage
GET /api/academic-years?status=active
POST /api/academic-years
PATCH /api/academic-years/:yearId
Body : { "label": "2024-2025", "startDate", "endDate", "status": "planned"|"active"|"archived" }
8. Classes (/api/school-classes)
Permission lecture : cursus.view
Permission écriture : program.manage
8.1 Lister (écran Classes)
GET /api/school-classes?q=&levelId=&academicYearId=&programId=&status=
Réponse item :
{
"id": "uuid",
"name": "L1 Informatique — Groupe A",
"specialty": "Informatique",
"program": { "id": "uuid", "name": "Licence Informatique" },
"level": { "id": "uuid", "name": "Licence 1" },
"academicYear": { "id": "uuid", "label": "2024-2025" },
"studentCount": 35,
"maxCapacity": 40,
"status": "active"
}
8.2 CRUD classe
POST /api/school-classes
GET /api/school-classes/:classId
PATCH /api/school-classes/:classId
DELETE /api/school-classes/:classId
Body création (modal UI) :
{
"name": "L1 Informatique — Groupe A",
"programId": "uuid",
"levelId": "uuid",
"academicYearId": "uuid",
"maxCapacity": 40,
"status": "active"
}
Règles :
studentCount≤maxCapacityà l’ajout d’étudiants ;- un étudiant = une classe active (
class_student.end_date IS NULL).
8.3 Membres de la classe
GET /api/school-classes/:classId/students
POST /api/school-classes/:classId/students → { "studentIds": ["uuid"] }
DELETE /api/school-classes/:classId/students/:studentId
POST : 409 si capacité dépassée ; 404 si étudiant inconnu.
9. Cohortes (/api/cohorts)
Permission lecture : cursus.view
Permission écriture : cohort.manage
9.1 Lister (écran Cohortes)
GET /api/cohorts?q=&programId=&academicYearId=&status=active|archived|all
Réponse item :
{
"id": "uuid",
"name": "Cohorte L1 Informatique",
"description": "Première année licence informatique",
"program": { "id": "uuid", "name": "Licence Informatique" },
"academicYear": { "id": "uuid", "label": "2024-2025" },
"studentCount": 42,
"status": "active",
"createdAt": "2025-09-01T08:00:00.000Z"
}
9.2 CRUD cohorte
POST /api/cohorts
GET /api/cohorts/:cohortId
PATCH /api/cohorts/:cohortId
DELETE /api/cohorts/:cohortId
Body création (modal UI) :
{
"name": "Cohorte L1 Informatique",
"description": "Première année licence informatique",
"programId": "uuid",
"academicYearId": "uuid",
"status": "active"
}
9.3 Membres de la cohorte
GET /api/cohorts/:cohortId/students
POST /api/cohorts/:cohortId/students → { "studentIds": ["uuid"] }
DELETE /api/cohorts/:cohortId/students/:studentId
10. Inscriptions aux cours
Les cours sont offerts par le département (pedagogy.course.department_id).
L’inscription ne lie pas le cours à un programme : elle lie des étudiants au cours.
Permission : student.manage + course.participants.view (lecture)
10.1 Lister les inscrits
GET /api/courses/:courseId/enrollments?q=&source=individual|cohort|class
Réponse :
{
"items": [
{
"studentId": "uuid",
"student": { "firstName": "Jean", "lastName": "Kouassi", "email": "…" },
"role": "student",
"enrolledAt": "2025-09-15",
"enrollmentSource": "cohort",
"sourceCohortId": "uuid",
"notificationSentAt": "2025-09-15T10:00:00.000Z"
}
]
}
10.2 Inscription individuelle
POST /api/courses/:courseId/enrollments/individual
Body :
{
"studentIds": ["uuid-1", "uuid-2"],
"notifyStudents": true
}
Réponse 201 :
{
"batchId": "uuid",
"enrollmentSource": "individual",
"requestedCount": 2,
"enrolledCount": 2,
"skippedCount": 0,
"skipped": [],
"notificationsQueued": 2
}
Comportement :
- idempotent : étudiant déjà inscrit →
skippedavec raisonalready_enrolled; - email si
notifyStudents: true(SMTP ou log console).
10.3 Inscription par cohorte
POST /api/courses/:courseId/enrollments/by-cohort
Body :
{
"cohortId": "uuid",
"notifyStudents": true
}
Résout tous les cohort_member actifs (left_at IS NULL). Même réponse batch que §10.2.
10.4 Inscription par classe
POST /api/courses/:courseId/enrollments/by-class
Body :
{
"schoolClassId": "uuid",
"notifyStudents": true
}
Résout tous les class_student actifs (end_date IS NULL).
10.5 Désinscription
DELETE /api/courses/:courseId/enrollments/:studentId
Réponse 204. Ne supprime pas l’appartenance classe/cohorte.
10.6 Email de notification
Template (i18n enrollment.course_welcome) — contenu :
| Champ | Source |
|---|---|
| Nom du cours | course.title |
| Code | course.code |
| Département | department.name |
| Enseignant | profil lead_teacher_id |
| Lien | {FRONTEND_URL}/courses/{courseId} |
Service : CourseEnrollmentService → port EmailSender (module communication existant).
11. Départements (/api/departments)
Permission lecture : cursus.view
Permission écriture : department.manage
GET /api/departments?siteId=
GET /api/departments/:departmentId
POST /api/departments
PATCH /api/departments/:departmentId
DELETE /api/departments/:departmentId
Body création :
{
"siteId": "uuid",
"name": "Informatique & Numérique",
"headId": "uuid"
}
DELETE → 409 si des programmes sont encore liés.
12. Matrice permissions
| Endpoint | Permission(s) |
|---|---|
GET /programs, /levels, /academic-years, /school-classes, /cohorts |
cursus.view |
POST/PATCH/DELETE /departments |
department.manage |
POST/PATCH/DELETE /programs |
program.manage |
PUT /programs/:id/curriculum |
cursus.manage |
POST/PATCH /levels |
level.manage |
POST/PATCH /academic-years |
academic_year.manage |
POST/PATCH/DELETE /school-classes |
program.manage |
POST/PATCH/DELETE /cohorts, membres cohorte |
cohort.manage |
| Membres classe | program.manage ou student.manage |
| Inscriptions cours | student.manage |
GET …/enrollments |
course.participants.view |
13. Clés i18n à ajouter
| Clé | Usage |
|---|---|
academic.program_not_found |
404 programme |
academic.level_not_found |
404 niveau |
academic.class_not_found |
404 classe |
academic.class_capacity_exceeded |
409 capacité |
academic.cohort_not_found |
404 cohorte |
academic.program_has_dependencies |
409 suppression |
enrollment.already_enrolled |
skip batch |
enrollment.course_not_found |
404 cours |
enrollment.student_not_found |
404 étudiant |
enrollment.course_welcome |
sujet email |
enrollment.course_welcome_body |
corps email |
14. Ordre d’implémentation
| Phase | Livrable |
|---|---|
| 1 | DDL 03_academic_structure_api.sql + entités TypeORM |
| 2 | /levels, /academic-years, /departments (lecture) |
| 3 | /school-classes + /cohorts + membres |
| 4 | /programs CRUD + liste |
| 5 | /programs/:id/curriculum |
| 6 | /courses/:id/enrollments/* + emails |
| 7 | Tests intégration + Swagger tags AcademicStructure |
15. Swagger
Tags OpenAPI :
Programs— formations / cursusProgramCurriculum— niveaux, semestres, UELevels— catalogue LMDAcademicYearsSchoolClassesCohortsCourseEnrollments— inscriptions groupées
Voir aussi
- Schéma base de données :
/database-schema - Concepts métier :
/academic-concepts - Architecture :
/architecture - Swagger interactif :
/docs