Aller au contenu

EPIDOCS API — Diagnostics

Vérifications rapides

État des conteneurs

ssh root@VPS_IP
cd /root/projects/mydocs/epidocs-api
docker compose ps

Résultat attendu :

NAME          IMAGE                                      STATUS                  PORTS
epidocs_api   ghcr.io/epitechafrik/epidocs-api:deploy    Up X hours (healthy)    0.0.0.0:7010->8000/tcp
epidocs_db    postgres:16                                Up X hours (healthy)

Health check

# Depuis le VPS
curl -s http://127.0.0.1:7010/

# Vérifier uniquement le status code
curl -o /dev/null -s -w "%{http_code}" http://127.0.0.1:7010/
# 200 = OK

Connectivité PostgreSQL

docker compose exec web python manage.py shell -c "
from django.db import connection
cursor = connection.cursor()
cursor.execute('SELECT 1')
print('PostgreSQL OK')
"

Vérifier les migrations

docker compose exec web python manage.py showmigrations | grep "\[ \]"
# Si des migrations ne sont pas appliquées, elles apparaîtront avec [ ]

Vérifier les droits (permissions)

docker compose exec web python manage.py shell -c "
from apps.user.models import User, Right
for u in User.objects.filter(is_staff=True):
    rights = u.rights.values_list('name', flat=True)
    print(f'{u.email} (super={u.is_superuser}): {list(rights)}')
"

Vérifier les templates de documents

# Lister les templates uploadés
docker compose exec web ls -la /epidocs/media/modeles/

# Vérifier les types de documents actifs (FR + EN)
docker compose exec web python manage.py shell -c "
from apps.document_type.models import DocumentType
for d in DocumentType.objects.all():
    en = d.modele_en or '—'
    print(f'{d.doc_id}: active={d.is_active}, FR={d.modele}, EN={en}')
"

Vérifier les documents sans version EN

# Documents générés avant le support multilingue (remote_url_en NULL)
docker compose exec web python manage.py shell -c "
from apps.document_type.models import GenDocumentHistory
count = GenDocumentHistory.objects.filter(remote_url_en__isnull=True).count()
total = GenDocumentHistory.objects.count()
print(f'{count}/{total} documents sans version EN')
"

Vérifier la configuration campus

# Lister les campus et leurs overrides de templates
docker compose exec web python manage.py shell -c "
from apps.campus.models import Campus, CampusDocumentType
for c in Campus.objects.all():
    overrides = CampusDocumentType.objects.filter(campus=c)
    print(f'Campus {c.name} ({c.code}): {overrides.count()} override(s)')
    for o in overrides:
        print(f'  {o.document_type.doc_id}: FR={bool(o.modele)}, EN={bool(o.modele_en)}')
"

# Vérifier le campus associé à un étudiant
docker compose exec web python manage.py shell -c "
from apps.student.models import Student
email = 'etudiant@epitech.bj'  # remplacer
s = Student.objects.get(login=email)
print(f'Campus: {s.campus}')
"

Vérifier les droits fins (Right / UserRight)

# Droits d'un admin en particulier
docker compose exec web python manage.py shell -c "
from apps.user.models import User
u = User.objects.get(email='admin@epitech.bj')  # remplacer
print(f'superuser={u.is_superuser}')
print(list(u.rights.values_list('name', flat=True)))
"

# Tous les droits existants et qui les possède
docker compose exec web python manage.py shell -c "
from apps.user.models import Right
for r in Right.objects.all():
    holders = list(r.userright_set.values_list('user__email', flat=True))
    print(f'{r.name}: {holders}')
"

Vérifier la présence campus (attendance)

# Agents de sécurité actifs
docker compose exec web python manage.py shell -c "
from apps.attendance.models import SecurityGuard
for g in SecurityGuard.objects.all():
    print(f'{g.name} - actif={g.is_active} - campus={g.campus}')
"

# Secret(s) QR configurés (nombre, pas la valeur)
docker compose exec web python -c "
from django.conf import settings
print(f'{len(settings.ATTENDANCE_QR_SECRETS)} secret(s) configuré(s)')
"

# Derniers scans (accès refusés en premier pour investiguer un incident)
docker compose exec web python manage.py shell -c "
from apps.attendance.models import AttendanceLog
for l in AttendanceLog.objects.filter(decision='denied').order_by('-scanned_at')[:10]:
    print(f'{l.scanned_at} - {l.student_nom_snapshot} {l.student_prenom_snapshot} - {l.denial_reason}')
"

# Résultat du dernier envoi de badges en masse (succès / échecs)
docker compose exec web python manage.py shell -c "
from apps.user.models import AuditLog
for action in ('attendance_qr_sent', 'attendance_qr_send_failed'):
    logs = AuditLog.objects.filter(action=action).order_by('-timestamp')[:5]
    print(f'--- {action} ---')
    for l in logs:
        print(f'{l.timestamp} - {l.target_id} - {l.details}')
"

Vérifier les overrides de templates par classe (ClassDocumentType)

docker compose exec web python manage.py shell -c "
from apps.document_type.models import ClassDocumentType
for o in ClassDocumentType.objects.select_related('document_type'):
    print(f'{o.classe} / {o.document_type.doc_id}: FR={bool(o.modele)}, EN={bool(o.modele_en)}')
"

Nombre d'é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\")
"

Logs

Logs Docker (temps réel)

# Logs du backend
docker compose logs -f web --tail 100

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

# Filtrer les erreurs
docker compose logs web --tail 500 | grep -i "error\|exception\|traceback"

Logs applicatifs

Les logs Django sont écrits dans /epidocs/logs/file.log à l'intérieur du conteneur :

docker compose exec web cat /epidocs/logs/file.log | tail -50

Logs via Grafana/Loki

Si Pedago Watch est configuré avec Promtail, les logs EPIDOCS sont accessibles dans Grafana :

  1. Aller sur grafana.epitools.bj
  2. Explorer → Loki
  3. Filtrer par container_name="epidocs_api"

Vérification des sessions

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')
for s in active[:10]:
    user = s.user or s.student
    print(f'  {user} - role={s.role} - expires={s.expires_at}')
"

Purger les sessions expirées

docker compose exec web python manage.py shell -c "
from apps.user.models import UserSession
from django.utils import timezone
expired = UserSession.objects.filter(expires_at__lt=timezone.now())
count = expired.count()
expired.update(is_valid=False)
print(f'{count} sessions expirées invalidées')
"

État du système

Mode maintenance

docker compose exec web python manage.py shell -c "
from apps.user.models import SystemStatus
s = SystemStatus.objects.first()
if s:
    print(f'Maintenance: {s.is_maintenance}')
    print(f'Message: {s.maintenance_message}')
    print(f'Fenêtre: {s.scheduled_start} → {s.scheduled_end}')
else:
    print('Aucun SystemStatus configuré')
"

Annonces actives

docker compose exec web python manage.py shell -c "
from apps.user.models import Announcement
from django.utils import timezone
active = Announcement.objects.filter(is_active=True)
for a in active:
    print(f'[{a.priority}] {a.title} - cible: {a.target} {a.target_class or \"\"}')
"

Espace disque

Media

# Taille totale du dossier media
du -sh /root/projects/mydocs/epidocs-api/media/

# Détail par sous-dossier
du -sh /root/projects/mydocs/epidocs-api/media/*/

Volume PostgreSQL

docker system df -v | grep epidocs

Nettoyage des images Docker inutilisées

docker image prune -af --filter "until=48h"

Environnement de développement local (macOS)

Qui écoute réellement sur un port (Colima / conflit de port)

docker ps ne montre que ce que Docker croit avoir mappé, pas qui répond réellement sur le port hôte — utile en cas de "Connection refused" malgré un mapping qui semble correct, ou d'erreur CORS qui n'en est pas une (processus orphelin qui squatte le port en IPv6) :

lsof -nP -iTCP:<port> -sTCP:LISTEN

Si le port ne répond pas alors que le conteneur est healthy sous Colima, redémarrer le conteneur (pas colima restart) :

docker restart <nom_du_conteneur>