# Audit du schéma de production (fiche F20) — procédure pas à pas

**Objectif** : comparer le schéma réel de la base de production aux migrations du dépôt, avant tout
chiffrage ferme. Répond aussi à la question ouverte de F7 (à quoi sert réellement `sub_manager` ?).

**Principe retenu : aucun identifiant ne sort de chez toi.** Tu exécutes un script sur le serveur,
il produit **un seul fichier texte**, tu me l'envoies. Le script lit tout seul les identifiants dans
le `.env` de l'application : tu n'as aucun mot de passe à taper ni à me transmettre.

**Ce que contient le fichier produit** : la structure des tables (aucune donnée), la liste des
migrations appliquées, des comptages, et des identifiants numériques pour les doublons.
**Aucune donnée personnelle** — pas de nom, pas d'email.

---

## Étape 1 — Se connecter au serveur

Ouvre le Terminal sur ton Mac et connecte-toi au VPS qui héberge l'API :

```bash
ssh <ton_utilisateur>@<adresse_du_serveur>
```

Si tu ne te souviens plus de l'adresse : c'est le serveur derrière `api.efektiv-academie.com`.

## Étape 2 — Aller dans le dossier de l'application

C'est le dossier qui contient le fichier `.env` et le dossier `app/`. Souvent quelque chose comme
`/var/www/...`. Pour le retrouver si besoin :

```bash
ls -d /var/www/*/ /home/*/  2>/dev/null
```

Puis place-toi dedans :

```bash
cd /chemin/vers/l-application
```

Vérifie que tu es au bon endroit — cette commande doit afficher `.env` et `artisan` :

```bash
ls -a | grep -E '^(\.env|artisan)$'
```

## Pourquoi la version du moteur est déterminante

La section 0 de l'audit n'est pas une formalité. Deux points en dépendent :

- **Requêtes récursives** — la résolution de la hiérarchie managériale (« Mes équipes »,
  issue EA-048, lot L4) s'écrit naturellement avec une requête récursive. Cette possibilité
  **n'existe qu'à partir de MySQL 8.0**. Sur une version antérieure, il faut soit remonter
  l'arbre par requêtes successives depuis PHP (plus lent, mais acceptable à cette
  volumétrie), soit maintenir une table de fermeture. À décider dès que la version est connue.
- **Écart développement / production** — l'environnement local tourne sur MySQL 9.6. Si la
  production est sur une version nettement plus ancienne, du code validé en local peut échouer
  en production. Une fois la version connue, aligner le local dessus.

## Étape 3 — Lancer l'audit

Copie-colle **tout le bloc ci-dessous d'un seul coup** dans le terminal, puis Entrée :

```bash
bash -s <<'AUDIT'
set -u
ENVF=".env"; [ -f "$ENVF" ] || { echo "Pas de .env ici — vérifie le dossier"; exit 1; }
get() { grep -E "^$1=" "$ENVF" | head -1 | cut -d= -f2- | tr -d '"'"'"' | tr -d "\r"; }
DBH=$(get DB_HOST); DBP=$(get DB_PORT); DBN=$(get DB_DATABASE); DBU=$(get DB_USERNAME); DBW=$(get DB_PASSWORD)
[ -z "$DBP" ] && DBP=3306
OUT="audit_schema_prod_$(date +%Y%m%d).txt"
export MYSQL_PWD="$DBW"
q() { mysql -h "$DBH" -P "$DBP" -u "$DBU" "$DBN" -e "$1" 2>&1; }
{
echo "=== BASE : $DBN  (généré le $(date)) ==="
echo; echo "=== 0. MOTEUR ET VERSION (determinant : cf. section precedente) ==="
q "SELECT VERSION() AS version, @@sql_mode AS sql_mode, @@default_storage_engine AS moteur, @@character_set_database AS charset, @@collation_database AS collation;"
q "SELECT COUNT(*) AS tables_non_innodb FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND ENGINE<>'InnoDB';"

echo; echo "=== 1. STRUCTURE COMPLETE (sans aucune donnee) ==="
mysqldump -h "$DBH" -P "$DBP" -u "$DBU" --no-data --skip-comments --skip-add-drop-table "$DBN" 2>&1
echo; echo "=== 2. MIGRATIONS APPLIQUEES ==="
q "SELECT migration FROM migrations ORDER BY id;"
echo; echo "=== 3. VOLUMETRIES ==="
q "SELECT (SELECT COUNT(*) FROM users) AS users,
          (SELECT COUNT(*) FROM enrollments) AS enrollments,
          (SELECT COUNT(*) FROM learner_lessons) AS learner_lessons,
          (SELECT COUNT(*) FROM quiz_scores) AS quiz_scores,
          (SELECT COUNT(*) FROM completed_courses) AS completed,
          (SELECT COUNT(*) FROM course_files) AS course_files,
          (SELECT COUNT(*) FROM courses) AS courses,
          (SELECT COUNT(*) FROM quizzes) AS quizzes;"
echo; echo "=== 4. DOUBLONS completed_courses (avant contrainte d'unicite) ==="
q "SELECT learner_id, course_id, COUNT(*) c FROM completed_courses
   GROUP BY learner_id, course_id HAVING c > 1 LIMIT 50;"
echo; echo "=== 5. ROLES REELLEMENT PRESENTS ET NOMBRE D'UTILISATEURS ==="
q "SELECT r.name, COUNT(mhr.model_id) AS nb_users FROM roles r
   LEFT JOIN model_has_roles mhr ON mhr.role_id = r.id GROUP BY r.name ORDER BY nb_users DESC;"
echo; echo "=== 6. UTILISATEURS SANS ROLE ==="
q "SELECT COUNT(*) AS users_sans_role FROM users u
   LEFT JOIN model_has_roles m ON m.model_id = u.id WHERE m.role_id IS NULL;"
echo; echo "=== 7. RATTACHEMENTS parent_id PAR ROLE (profondeur hierarchique) ==="
q "SELECT r.name AS role_parent, COUNT(DISTINCT u.id) AS nb_rattaches
   FROM users u JOIN users p ON p.id = u.parent_id
   JOIN model_has_roles m ON m.model_id = p.id JOIN roles r ON r.id = m.role_id
   GROUP BY r.name;"
echo; echo "=== 8. COLONNES SUSPECTES (presentes en base ?) ==="
q "SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.COLUMNS
   WHERE TABLE_SCHEMA='$DBN' AND (
     (TABLE_NAME='users' AND COLUMN_NAME IN ('is_active','cta_url')) OR
     (TABLE_NAME='enrollments' AND COLUMN_NAME='is_complete') OR
     (TABLE_NAME='sections' AND COLUMN_NAME IN ('description','start_date','frequency','start_after_enroll','required_score')) OR
     (TABLE_NAME='lessons' AND COLUMN_NAME='order_no') OR
     (TABLE_NAME='quizzes' AND COLUMN_NAME='order_no') OR
     (TABLE_NAME='courses' AND COLUMN_NAME='category_id'))
   ORDER BY TABLE_NAME, COLUMN_NAME;"
echo; echo "=== FIN ==="
} > "$OUT" 2>&1
unset MYSQL_PWD
echo "Termine. Fichier genere : $(pwd)/$OUT  ($(wc -c < "$OUT") octets)"
AUDIT
```

Le script affiche à la fin le chemin du fichier généré, par exemple :
`Termine. Fichier genere : /var/www/api/audit_schema_prod_20260805.txt`

## Étape 4 — Rapatrier le fichier sur ton Mac

**Ouvre un second Terminal** (sans fermer la session SSH), et lance :

```bash
scp <ton_utilisateur>@<adresse_du_serveur>:/chemin/complet/audit_schema_prod_*.txt ~/Desktop/
```

Le fichier arrive sur ton Bureau.

## Étape 5 — Me l'envoyer

Glisse le fichier dans la conversation, ou dis-moi simplement « le fichier est sur le Bureau » —
je le lirai directement.

---

## Si quelque chose bloque

| Symptôme | Cause probable et solution |
|---|---|
| `Pas de .env ici` | Tu n'es pas dans le dossier de l'application — reviens à l'étape 2 |
| `mysqldump: command not found` | Le client MySQL n'est pas installé sur le serveur : `sudo apt install mysql-client` |
| `Access denied for user` | L'utilisateur du `.env` n'a pas le droit `SELECT` sur `information_schema` — dis-le-moi, j'adapte le script |
| La base n'est pas sur le même serveur | Le script fonctionne quand même : il utilise le `DB_HOST` du `.env`, quel qu'il soit |
| Tu préfères ne rien exécuter | Envoie-moi seulement un `mysqldump --no-data` de la base : j'aurai déjà 80 % de ce qu'il me faut |

## Note de sécurité

Le script n'écrit rien : il ne fait que des `SELECT` et un export de structure. Le mot de passe est
passé par la variable d'environnement `MYSQL_PWD` (jamais visible dans la liste des processus) et
effacé à la fin. Aucune donnée personnelle n'est extraite : ni nom, ni prénom, ni email.
