Aller au contenu

Aide-mémoire — EPIDOCS API

Référence rapide pour le backend Django de EPIDOCS.

Accès rapides

Élément Lien
API prod api.mydocs.epitools.bj
Repo GitHub

Ports et conteneurs

Service Port hôte Port conteneur Image
API (Gunicorn) 127.0.0.1:7010 8000 ghcr.io/epitechafrik/epidocs-api:deploy
PostgreSQL 16 non exposé 5432 postgres:16

Chemins VPS

/root/projects/mydocs/epidocs-api/

Commandes essentielles

Vérification santé

curl -o /dev/null -s -w "%{http_code}" http://127.0.0.1:7010/
# 200 = OK

Logs

cd /root/projects/mydocs/epidocs-api

# Logs applicatifs
docker compose logs -f web --tail 100
docker compose logs web --tail 500 | grep -i "error\|exception\|traceback"

# Logs Django (fichier dans conteneur)
docker compose exec web cat /epidocs/logs/file.log | tail -50

# Logs PostgreSQL
docker compose logs -f db --tail 50

Base de données

# Vérifier la connectivité PostgreSQL
docker compose exec web python manage.py shell -c \
  "from django.db import connection; connection.cursor().execute('SELECT 1'); print('OK')"

# Migrations en attente
docker compose exec web python manage.py showmigrations | grep "\[ \]"

# Backup PostgreSQL
docker compose exec db pg_dump -U postgres epidocs_db > backup_$(date +%Y%m%d).sql
docker compose exec db pg_dump -U postgres -Fc epidocs_db > backup_$(date +%Y%m%d).dump

# Backup media (templates Word + documents générés)
tar czf media_backup_$(date +%Y%m%d).tar.gz media/

Corrections manuelles sur un étudiant

Passer par l'admin Django (/admin/student/student/), pas par SQL direct : le formulaire expose tous les champs de Student, et civilite y est une liste déroulante Monsieur/Madame depuis PR #13 — plus sûr qu'une valeur texte libre pour une correction ponctuelle.

Secours SQL direct (opération en masse, ou admin indisponible) :

docker compose exec db psql -U postgres -d epidocs_db -c \
  "SELECT login, nom, prenom, civilite, promo FROM student_student WHERE login = 'LOGIN_ETUDIANT';"

docker compose exec db psql -U postgres -d epidocs_db -c \
  "UPDATE student_student SET promo = '2024' WHERE login = 'LOGIN_ETUDIANT';"

Un document généré ne se corrige pas lui-même

L'année de délivrance et l'accord grammatical d'un document sont calculés à partir de civilite/promo au moment de la génération — corriger la donnée ne met pas à jour les PDF déjà générés. Voir Erreurs courantes → civilité/année incohérente.

Diagnostics utiles

# Étudiants par classe
docker compose exec web python manage.py shell -c "
from apps.student.models import Student
from django.db.models import Count
stats = Student.objects.filter(account_suspended=0).values('section').annotate(count=Count('login')).order_by('section')
for s in stats: print(f\"{s['section']}: {s['count']} étudiants\")"

# Sessions actives
docker compose exec web python manage.py shell -c "
from apps.user.models import UserSession
from django.utils import timezone
active = UserSession.objects.filter(is_valid=True, expires_at__gt=timezone.now())
print(f'{active.count()} sessions actives')"

# Mode maintenance
docker compose exec web python manage.py shell -c "
from apps.user.models import SystemStatus
s = SystemStatus.objects.first()
print(f'Maintenance: {s.is_maintenance}' if s else 'Pas de SystemStatus')"

# Templates documents
docker compose exec web ls -la /epidocs/media/modeles/

# Espace disque media
du -sh /root/projects/mydocs/epidocs-api/media/

Rollback

cd /root/projects/mydocs/epidocs-api
# Lister les images disponibles
docker images ghcr.io/epitechafrik/epidocs-api --format "table {{.Tag}}\t{{.CreatedAt}}"
# Modifier le tag dans docker-compose.yml puis :
docker compose pull web
docker compose up -d --no-deps web

Pipelines CI/CD

gh run list --repo EpitechAfrik/epidocs-api
gh run view RUN_ID --repo EpitechAfrik/epidocs-api --log-failed

Stack résumée

Composant Technologie
Framework Django 5 + Django REST Framework
BDD PostgreSQL 16
Auth JWT cookie HttpOnly + Azure AD (MSAL)
PDF docxtpl + LibreOffice
QR Codes qrcode + Pillow
WSGI Gunicorn (3 workers, timeout 120s)
Stockage Local media/ ou Cloudinary

Volumes Docker

Volume Usage
epidocs-api_postgres_data (externe) Données PostgreSQL
./media (bind mount) Templates Word + documents
static_data Fichiers statiques Django

Variables d'environnement clés

Ne jamais commiter les secrets

Les .env sont sur le VPS uniquement.

Variable Description
SECRET_KEY Secret Django
POSTGRES_PASSWORD Mot de passe PostgreSQL
JWT_SECRET_KEY Secret JWT
JWT_COOKIE_DOMAIN .epitools.bj
AZURE_CLIENT_ID / AZURE_CLIENT_SECRET / AZURE_TENANT_ID Azure AD SSO
RESEND_API_KEY Token API Resend (emails)
ALLOWED_HOSTS api.mydocs.epitools.bj,localhost
ATTENDANCE_QR_SECRET Secret(s) HMAC des QR de présence, liste séparée par virgules (rotation) — défaut SECRET_KEY

Date d'expiration du secret Azure AD — À REMPLIR

AZURE_CLIENT_SECRET expire à une date fixée dans Azure Portal (App Registrations → Certificates & secrets). Date d'expiration actuelle : à vérifier et noter ici. Si ce secret expire sans renouvellement, plus personne ne peut se connecter (admins et étudiants) — voir Erreurs courantes pour la procédure de rotation, et Continuité — accès externes pour donner l'accès Azure Portal à une seconde personne.

Routes API principales

Route Description
/auth/sign_in Connexion
/auth/callback Callback Azure AD
/auth/me Utilisateur courant
/student/auth/me Étudiant courant
/documents/generate-document Génération PDF
/documents/admin/batch-generate Génération PDF en lot
/student/upload-csv/ Import étudiants CSV
/admin/system-status Mode maintenance
/admin/audit-logs Journal d'audit
/integrations/v1/docs Doc live de l'API Zeno (ReDoc)
/integrations/v1/documents/generate Génération de doc par un service (clé API)
/attendance/auth Connexion agent de sécurité (PIN)
/attendance/scan Scanner un QR de présence
/attendance/qrcodes/audience Aperçu des destinataires avant envoi de badges
/attendance/qrcodes/send Envoi en masse des badges (async, 202)
/documents/admin/classes/:classe/templates Override de template par classe (ClassDocumentType)

Droits admin (Right / UserRight)

20 droits au total (liste faisant autorité : apps/user/management/commands/seed_rights.py). Seuls can_upload_tepitech, can_upload_user, can_generate, can_generate_for_student sont auto-accordés aux admins non-superadmin (base_rights) — les 16 autres (dont can_manage_attendance, can_manage_document_types, can_suspend_students, manage_campus, can_view_students...) doivent être accordés explicitement. Détail complet : overview.md § Système de permissions.

# Voir/accorder les droits d'un admin
docker compose exec web python manage.py shell -c "
from apps.user.models import User, Right, UserRight
u = User.objects.get(email='admin@epitech.bj')
print(list(u.rights.values_list('name', flat=True)))
"
docker compose exec web python manage.py shell -c "
from apps.user.models import User, Right, UserRight
u = User.objects.get(email='admin@epitech.bj')
r = Right.objects.get(name='can_manage_attendance')
UserRight.objects.get_or_create(user=u, right=r)
"

# Re-seeder les droits (créés si absents, jamais retirés)
docker compose exec web python manage.py seed_rights

Non-délégable (IsSuperAdmin uniquement, pas de Right possible) : créer un compte admin (CreateUserView), assigner un gestionnaire de campus (CampusManagerListView/CampusManagerDeleteView).

Présence campus (attendance)

Auth séparée : cookie attendance_session (agent de sécurité par PIN), distincte de auth_token (admin/étudiant). QR = signature:login, HMAC-SHA256 signé avec ATTENDANCE_QR_SECRETS (1er secret de la liste = signature, tous acceptés en vérification → rotation possible).

# Créer/lister les agents de sécurité
docker compose exec web python manage.py shell -c "
from apps.attendance.models import SecurityGuard
for g in SecurityGuard.objects.all(): print(g.name, g.is_active)
"

# Compter les secrets QR actifs (rotation en cours si > 1)
docker compose exec web python -c "
from django.conf import settings
print(len(settings.ATTENDANCE_QR_SECRETS))
"

# Vérifier les envois de badges récents (succès/échec)
docker compose exec db psql -U postgres -d epidocs_db -c \
  "SELECT action, target_id, timestamp FROM user_auditlog WHERE action LIKE 'attendance_qr_%' ORDER BY timestamp DESC LIMIT 20;"

Toujours prévisualiser avant d'envoyer

POST /attendance/qrcodes/audience (lecture seule) avant POST /attendance/qrcodes/send — un envoi par section seule inclut par défaut les diplômés et suspendus ; _build_qr_audience() les exclut désormais par défaut, mais vérifier la liste reste la meilleure protection contre un envoi non désiré (incident réel du 2026-08-29).

Intégration Zeno (clés API)

API service-à-service authentifiée par header Authorization: Api-Key <clé>. Doc live : /integrations/v1/docs.

# Créer un compte de service + clé (la clé s'affiche UNE seule fois)
docker compose exec web python manage.py create_service_account zeno --description "Backend Zeno"

# Clé lecture seule (scopes: read uniquement → ne peut pas générer)
docker compose exec web python manage.py create_service_account zeno-readonly --scopes read

# Lister les comptes de service et l'état de leurs clés
docker compose exec db psql -U postgres -d epidocs_db -c \
  "SELECT s.name, k.prefix, k.is_active, k.last_used_at, k.revoked_at
   FROM integrations_serviceaccount s
   JOIN integrations_serviceapikey k ON k.service_id = s.id ORDER BY s.name;"

# Audit des actions d'un service (génération + téléchargement)
docker compose exec db psql -U postgres -d epidocs_db -c \
  "SELECT action, actor_email, target_id, ip_address, timestamp
   FROM user_auditlog WHERE actor_role='service' ORDER BY timestamp DESC LIMIT 20;"

# Tester une clé (attendu: 400 = auth OK, 401 = clé invalide)
curl -s -o /dev/null -w "%{http_code}\n" -X POST \
  http://127.0.0.1:7010/integrations/v1/documents/generate \
  -H "Authorization: Api-Key LA_CLE" -H "Content-Type: application/json" -d '{}'

Révocation / rotation d'une clé : depuis le dashboard (super-admin) ou via POST /integrations/v1/admin/keys/:id/revoke (ou /rotate).