EPIDOCS API — Backend
Description
EPIDOCS est une plateforme de gestion documentaire pour EPITECH Bénin. Elle permet aux étudiants de générer leurs documents administratifs (certificats de scolarité, attestations, lettres de recommandation) et aux administrateurs de gérer les étudiants, les templates, les migrations de promotions et la génération en lot.
Fonctionnalités principales
- Authentification Azure AD : SSO Microsoft pour étudiants et admins, sessions JWT en cookie HttpOnly
- Génération de documents : templates Word (.docx) remplis dynamiquement, convertis en PDF avec QR code de vérification
- Génération en lot (batch) : génération de documents pour toute une promotion, export en ZIP
- Gestion des étudiants : import CSV, migration de promotions (PGE1→PGE2, etc.), suspension de comptes
- Demandes de modification : les étudiants peuvent demander la correction de leurs informations personnelles
- Réclamations : système de tickets avec pièces jointes
- Mode maintenance : middleware qui bloque l'accès avec maintenance planifiée
- Annonces : système d'annonces ciblées par classe ou global
- Audit log : traçabilité de toutes les actions admin
Architecture
Stack technique
| Composant |
Technologie |
| Framework |
Django 5 + Django REST Framework |
| Base de données |
PostgreSQL 16 |
| Authentification |
JWT (cookie HttpOnly) + Azure AD (MSAL) |
| Génération de documents |
docxtpl + LibreOffice (conversion PDF) |
| QR Codes |
qrcode + Pillow |
| Stockage fichiers |
Local (media/) ou Cloudinary (remote) |
| Conteneur |
Docker (python:3.12 + LibreOffice) |
| WSGI |
Gunicorn (3 workers, timeout 120s) |
| API format |
CamelCase JSON (djangorestframework-camel-case) |
Routes API
Authentification (/auth/, /student/auth/)
| Méthode |
Route |
Description |
| GET |
/auth/sign_in |
Initie le flow Azure AD |
| GET |
/auth/callback |
Callback OAuth Azure AD |
| GET |
/auth/me |
Infos de l'admin connecté |
| POST |
/auth/sign_out |
Déconnexion |
| GET |
/student/auth/me |
Infos de l'étudiant connecté |
Documents (/documents/)
| Méthode |
Route |
Description |
| GET |
/documents/available-document-types |
Documents disponibles pour une classe |
| POST |
/documents/generate-document |
Générer un document (étudiant) |
| GET |
/documents/history |
Historique des documents générés |
| GET |
/documents/tracking/:doc_ref |
Vérification d'un document via QR code |
| POST |
/documents/admin/generate-for-student |
Générer un doc pour un étudiant (admin) |
| POST |
/documents/admin/batch-generate |
Génération en lot pour une classe (admin) |
| GET |
/documents/admin/dashboard/stats/ |
Statistiques du dashboard |
| GET |
/documents/admin/student-history/:email/ |
Historique d'un étudiant (admin) |
| DELETE |
/documents/admin/delete-document/:tracking_code/ |
Supprimer un document |
Étudiants (/student/)
| Méthode |
Route |
Description |
| POST |
/student/upload-csv/ |
Import d'étudiants via CSV |
| POST |
/student/upload-tepitech-csv/ |
Import des scores TEPITECH |
| POST |
/student/migrate-students/ |
Migration de promotion |
| GET |
/student/admin/list/ |
Liste paginée des étudiants |
| POST |
/student/pending-students |
Ajouter un étudiant en attente |
| POST |
/student/validate-students |
Valider les étudiants en attente |
| POST |
/student/info-change-request/ |
Demande de modification (étudiant) |
| GET |
/student/admin/info-change-requests/ |
Liste des demandes (admin) |
Administration (/admin/)
| Méthode |
Route |
Description |
| GET/PUT |
/admin/system-status |
État du système / mode maintenance |
| GET/POST |
/admin/announcements |
Gestion des annonces |
| GET |
/admin/audit-logs |
Journal d'audit |
| GET |
/admin/analytics |
Tableau de bord analytique |
| POST |
/admin/backup/export |
Export de la base de données |
| POST |
/admin/backup/import |
Import de la base de données |
Réclamations (/complaints/)
| Méthode |
Route |
Description |
| GET/POST |
/complaints/student |
Réclamations de l'étudiant |
| GET/POST |
/complaints/admin |
Gestion des réclamations (admin) |
Modèle de données
Student
├── login (PK, email EPITECH)
├── civilite, nom, prenom
├── date_naissance, ville_naissance, nationalite
├── section (PGE1, PGE2, PGE3, etc.)
├── promo, annee, ville_etude
├── inscription_year, inscription_month
├── nom_pere, prenom_pere, nom_mere, prenom_mere
├── specialite
├── PGE3_status, PGE5_status (success/null)
└── account_suspended (0/1)
User (Admin)
├── email (PK), first_name, last_name
├── is_staff, is_superuser
├── rights → ManyToMany(Right)
└── azure_id
DocumentType
├── doc_id (certificat_scolarite, attestation_passage_pge, etc.)
├── modele (fichier .docx template)
├── is_active
└── created_by → FK(User)
TrackingCode
├── tracking_code (PK, UUID)
├── doc_ref (REF/CS/abc/def)
├── generated_by → FK(Student)
└── generated_by_admin → FK(User)
GenDocumentHistory
├── tracking_code → OneToOne(TrackingCode)
├── doc_id, current_classe
├── generation_count
├── remote_url (chemin local ou Cloudinary public_id)
├── generated_by → FK(Student)
└── generated_by_admin → FK(User)
UserSession
├── session_id (UUID)
├── user → FK(User) ou student → FK(Student)
├── role (admin/student)
├── is_valid, expires_at
└── ip_address, user_agent
AuditLog
├── action (login, document_generated, batch_generate_by_admin, etc.)
├── actor_email, actor_role
├── target_type, target_id
├── details (JSON)
└── ip_address, timestamp
SystemStatus
├── is_maintenance (bool)
├── maintenance_message
├── scheduled_start, scheduled_end
└── updated_by → FK(User)
Announcement
├── title, message
├── target (all/students/admins)
├── target_class (PGE1, PGE2, etc.)
├── priority (info/warning/danger)
├── is_active, start_date, end_date
└── created_by → FK(User)
Système de permissions
| Droit |
Description |
Superadmin |
Admin |
can_upload_tepitech |
Import des scores TEPITECH |
oui |
oui |
can_upload_user |
Import CSV des étudiants |
oui |
oui |
can_generate |
Générer des documents |
oui |
oui |
can_generate_for_student |
Générer pour un étudiant |
oui |
oui |
can_migrate_students |
Migrer les promotions |
oui |
non |
can_batch_generate |
Génération en lot |
oui |
non |
Les droits sont initialisés par la commande python manage.py seed_rights (exécutée automatiquement au démarrage).
Types de documents
| ID |
Nom |
Classes autorisées |
certificat_scolarite |
Certificat de scolarité |
Toutes |
attestation_passage_pge |
Attestation de passage PGE |
PGE2+ |
attestation_validation_tepitech |
Attestation TEPITECH |
Toutes (score requis) |
attestation_fin_succes_bachelor |
Attestation fin de Bachelor |
PGE3 (status=success) |
attestation_fin_success_master |
Attestation fin de Master |
PGE5 (status=success) |
lettre_recommandation |
Lettre de recommandation |
PGE2+ |
attestation_passage_coding_academy |
Attestation Coding Academy |
CodingAcademy |
Diagrammes
Architecture globale
graph TB
Student[Étudiant / Navigateur]
Admin[Admin / Navigateur]
Frontend["Frontend React<br/>api.mydocs.epitools.bj"]
Nginx["Nginx<br/>Reverse proxy + TLS"]
Backend["Backend API<br/>Django :8000"]
PostgreSQL["PostgreSQL 16<br/>(conteneur)"]
AzureAD["Azure AD<br/>(SSO Microsoft)"]
LibreOffice["LibreOffice<br/>(conversion PDF)"]
Media["Media<br/>(bind mount)"]
Student --> Nginx
Admin --> Nginx
Nginx --> Frontend
Nginx --> Backend
Backend --> PostgreSQL
Backend --> AzureAD
Backend --> LibreOffice
Backend --> Media
Flux de génération de document
sequenceDiagram
participant User as Étudiant/Admin
participant API as Backend API
participant DB as PostgreSQL
participant TPL as DocxTemplate
participant LO as LibreOffice
participant FS as Stockage (media/)
User->>API: POST /documents/generate-document
API->>DB: Vérifier historique existant
alt Document déjà généré
API->>FS: Récupérer le PDF existant
FS-->>API: PDF
else Nouveau document
API->>DB: Générer tracking code + référence
API->>TPL: Remplir template .docx (attributs étudiant)
TPL-->>API: Fichier .docx rempli
API->>LO: Convertir .docx → .pdf
LO-->>API: Fichier .pdf
API->>API: Ajouter QR code, nettoyer pages vides
API->>FS: Sauvegarder le PDF
API->>DB: Créer GenDocumentHistory
end
API-->>User: PDF (Content-Disposition: attachment)
Flux de génération en lot (batch)
sequenceDiagram
participant Admin as Admin
participant API as Backend API
participant DB as PostgreSQL
participant Gen as Génération unitaire
participant ZIP as Archive ZIP
Admin->>API: POST /documents/admin/batch-generate
API->>DB: Récupérer tous les étudiants de la classe
loop Pour chaque étudiant
API->>DB: Vérifier si document existe
alt Existe
API->>Gen: Récupérer document existant
else N'existe pas
API->>Gen: Générer nouveau document
end
Gen-->>API: PDF
API->>ZIP: Ajouter au ZIP
end
API->>DB: AuditLog (batch_generate_by_admin)
API-->>Admin: ZIP (Content-Disposition: attachment)
Middleware de maintenance
graph TD
Request[Requête entrante] --> CheckPath{Path exempté ?}
CheckPath -->|Oui| Pass[Laisser passer]
CheckPath -->|Non| CheckMaintenance{Mode maintenance actif ?}
CheckMaintenance -->|Non| Pass
CheckMaintenance -->|Oui| CheckAdmin{Utilisateur admin ?}
CheckAdmin -->|Oui| Pass
CheckAdmin -->|Non| Block[503 Service Unavailable]
Paths exemptés du mode maintenance :
/auth/sign_in, /auth/callback, /auth/me
/student/auth/me
/admin/system-status, /admin/announcements
Docker
Dockerfile
L'image utilise python:3.12 avec LibreOffice installé (nécessaire pour la conversion DOCX → PDF). L'application ne tourne pas en tant qu'utilisateur non-root (à améliorer).
docker-compose.yml (production)
services:
db:
restart: always
image: postgres:16
container_name: epidocs_db
volumes:
- epidocs-api_postgres_data:/var/lib/postgresql/data/
environment:
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_DB: ${POSTGRES_DB:-epidocs_db}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
retries: 5
start_period: 30s
web:
image: ghcr.io/epitechafrik/epidocs-api:deploy
container_name: epidocs_api
restart: unless-stopped
ports:
- "7010:8000"
env_file:
- .env
volumes:
- ./media:/epidocs/media
- static_data:/epidocs/staticfiles
command: >
bash -c "python manage.py migrate &&
python manage.py seed_rights &&
python manage.py collectstatic --noinput &&
gunicorn project.wsgi:application --bind 0.0.0.0:8000 --workers 3 --timeout 120"
depends_on:
db:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
epidocs-api_postgres_data:
external: true
static_data:
Points importants :
- Volume externe
epidocs-api_postgres_data : les données PostgreSQL persistent entre les déploiements. Ce volume doit exister avant le premier docker compose up.
- Bind mount
./media : les templates Word et les documents générés sont stockés sur le disque du VPS, pas dans le conteneur. Cela permet de conserver les templates entre les déploiements.
- Startup : au démarrage, le conteneur exécute
migrate + seed_rights + collectstatic avant de lancer Gunicorn.
- Port :
7010 côté hôte, accessible uniquement via Nginx.
- Le port PostgreSQL n'est pas exposé sur l'hôte (sécurité).
Variables d'environnement
Django
| Variable |
Description |
Exemple |
DJANGO_ENV |
Mode d'exécution |
production |
SECRET_KEY |
Clé secrète Django |
chaîne aléatoire longue |
ALLOWED_HOSTS |
Hôtes autorisés (séparés par virgule) |
api.mydocs.epitools.bj,localhost |
Base de données
| Variable |
Description |
Exemple |
POSTGRES_DB |
Nom de la base |
epidocs_db |
POSTGRES_USER |
Utilisateur PostgreSQL |
postgres |
POSTGRES_PASSWORD |
Mot de passe PostgreSQL |
CHANGE_ME |
POSTGRES_HOST |
Hôte PostgreSQL |
db (nom du service Docker) |
Authentification
| Variable |
Description |
Exemple |
JWT_SECRET_KEY |
Clé secrète JWT (optionnel, défaut = SECRET_KEY) |
chaîne aléatoire |
JWT_COOKIE_DOMAIN |
Domaine du cookie (prod uniquement) |
.epitools.bj |
Azure AD
| Variable |
Description |
Exemple |
AZURE_CLIENT_ID |
Client ID de l'App Registration |
UUID |
AZURE_CLIENT_SECRET |
Client Secret Azure |
CHANGE_ME |
AZURE_TENANT_ID |
Tenant ID Azure |
UUID |
CALLBACK_URL |
URL de callback OAuth |
https://api.mydocs.epitools.bj/auth/callback |
URLs
| Variable |
Description |
Exemple |
WEB_URL |
URL du frontend |
https://app.epitools.bj |
SITE_URL |
URL de l'API |
https://api.mydocs.epitools.bj |
Email (optionnel)
| Variable |
Description |
Exemple |
RESEND_API_KEY |
Clé API Resend |
re_xxxxx |
SMTP_SERVER |
Serveur SMTP |
smtp.example.com |
SMTP_PORT |
Port SMTP |
465 |
SMTP_USERNAME |
Utilisateur SMTP |
no-reply@epitech.bj |
SMTP_PASSWORD |
Mot de passe SMTP |
CHANGE_ME |
Cloudinary (si STORAGE_TYPE=remote)
| Variable |
Description |
Exemple |
CLOUDINARY_CLOUD_NAME |
Nom du cloud |
my-cloud |
CLOUDINARY_API_KEY |
Clé API |
123456 |
CLOUDINARY_API_SECRET |
Secret API |
CHANGE_ME |
Déploiement
Pipeline CI/CD
Le déploiement est déclenché automatiquement sur push vers la branche deploy via GitHub Actions.
graph LR
Push["Push sur deploy"] --> CI["CI: Build & Test"]
CI --> Docker["Build image Docker"]
Docker --> GHCR["Push sur GHCR"]
GHCR --> Deploy["SCP + SSH sur VPS"]
Deploy --> Health["Health check"]
Étapes détaillées :
- CI : Python 3.12, PostgreSQL 16 de test, linting (flake8), vérification des migrations, tests
- Build Docker : construction de l'image et push sur
ghcr.io/epitechafrik/epidocs-api:deploy
- Déploiement : SCP du
docker-compose.prod.yml et .env sur le VPS, puis docker compose pull && docker compose up -d
- Health check : vérification que le conteneur répond sur le port 7010
Secrets GitHub Actions
| Secret |
Description |
SSH_PRIVATE_KEY |
Clé SSH pour accès au VPS |
VPS_IP |
Adresse IP du VPS |
VPS_USER |
Utilisateur SSH (root) |
ENV_FILE |
Contenu complet du fichier .env |
GITHUB_TOKEN |
Token pour GHCR (automatique) |
Rollback
Quand faire un rollback
- Le déploiement a cassé une fonctionnalité critique
- Le health check échoue après déploiement
- Régression détectée en production
Procédure
# 1. Se connecter au VPS
ssh root@VPS_IP
# 2. Aller dans le répertoire du projet
cd /root/projects/mydocs/epidocs-api
# 3. Voir les images disponibles
docker images ghcr.io/epitechafrik/epidocs-api --format "table {{.Tag}}\t{{.CreatedAt}}"
# 4. Modifier le tag dans docker-compose.yml
# Changer ghcr.io/epitechafrik/epidocs-api:deploy par le tag voulu
nano docker-compose.yml
# 5. Redéployer
docker compose pull
docker compose up -d
# 6. Vérifier
curl -s http://127.0.0.1:7010/
docker compose logs web --tail 30
Après le rollback
- Investiguer la cause sur la branche
deploy
- Corriger et pusher le fix
- Le pipeline redéploiera automatiquement
Backups
PostgreSQL
La base de données utilise un volume Docker externe (epidocs-api_postgres_data). Les données persistent entre les redéploiements.
Backup manuel
# Depuis le VPS
cd /root/projects/mydocs/epidocs-api
# Dump complet
docker compose exec db pg_dump -U postgres epidocs_db > backup_$(date +%Y%m%d).sql
# Dump compressé
docker compose exec db pg_dump -U postgres -Fc epidocs_db > backup_$(date +%Y%m%d).dump
Restauration
# Restaurer un dump SQL
docker compose exec -T db psql -U postgres epidocs_db < backup_YYYYMMDD.sql
# Restaurer un dump compressé
docker compose exec -T db pg_restore -U postgres -d epidocs_db backup_YYYYMMDD.dump
Backup via l'API (superadmin)
# Export via l'API (nécessite cookie auth_token)
curl -b "auth_token=TOKEN" https://api.mydocs.epitools.bj/admin/backup/export -o backup.json
# Import
curl -b "auth_token=TOKEN" -X POST -F "file=@backup.json" https://api.mydocs.epitools.bj/admin/backup/import
Les fichiers media sont dans un bind mount ./media sur le VPS. Pour les sauvegarder :
# Depuis le VPS
cd /root/projects/mydocs/epidocs-api
tar czf media_backup_$(date +%Y%m%d).tar.gz media/