Aller au contenu

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 :

  1. CI : Python 3.12, PostgreSQL 16 de test, linting (flake8), vérification des migrations, tests
  2. Build Docker : construction de l'image et push sur ghcr.io/epitechafrik/epidocs-api:deploy
  3. Déploiement : SCP du docker-compose.prod.yml et .env sur le VPS, puis docker compose pull && docker compose up -d
  4. 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

  1. Investiguer la cause sur la branche deploy
  2. Corriger et pusher le fix
  3. 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

Media (templates et documents générés)

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/