Paillasse/portail/app/Console/Commands/PurgerClassesArchivees.php

77 lines
3 KiB
PHP
Raw Normal View History

<?php
namespace App\Console\Commands;
use App\Models\Classe;
use App\Models\RenduDevoir;
use App\Services\CacheTableauDeBordProf;
use App\Services\Stockage\StockageTravauxEleves;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
/**
* RGPD (CLAUDE.md, étape 14) : supprime les classes archivées depuis
* 90 jours, avec en cascade leurs élèves, séances, sessions de lab, machines,
* corrections, quiz et devoirs maison (chantier « vrai cockpit »), ainsi que
* les annonces, observations, le chat élève ↔ prof (séparation vitrine/
* application, 2026-10-13), les événements d'examen (2026-10-14) et le
* dossier WebDAV de chaque élève (bloc C, 2026-10-14). Le compte du prof
* est conservé. Le journal ne garde que des identifiants et des nombres,
* jamais de nom d'élève.
*
* Les sauvegardes chiffrées contenant encore ces données expirent d'elles-
* mêmes au plus 4 semaines plus tard (rotation, docs/runbooks/sauvegardes.md).
*/
class PurgerClassesArchivees extends Command
{
protected $signature = 'classes:purger-archivees';
protected $description = 'Supprime les classes archivées depuis 90 jours et les données de leurs élèves';
public function handle(): void
{
$classes = Classe::whereNotNull('archivee_a')
->where('archivee_a', '<=', now()->subDays(Classe::DELAI_PURGE_JOURS))
->withCount('eleves')
->with('eleves:id,classe_id')
->get();
$purgees = 0;
foreach ($classes as $classe) {
// Ne peut arriver qu'en cas d'anomalie (un lab expire en 45 min) :
// on ne supprime pas une session dont la stack Heat vivrait encore.
if ($classe->aDesLabsEnCours()) {
Log::warning('Purge RGPD reportée : lab en cours', ['classe_id' => $classe->id]);
continue;
}
// Les fichiers d'Object Storage et les dossiers WebDAV ne sont
// pas liés par une clé étrangère : à supprimer nous-mêmes avant
// la cascade en base.
RenduDevoir::whereHas('devoir', fn ($q) => $q->where('classe_id', $classe->id))
->whereNotNull('fichier_chemin')
->pluck('fichier_chemin')
->each(fn (string $chemin) => StockageTravauxEleves::supprimer($chemin));
foreach ($classe->eleves as $eleve) {
Storage::disk('webdav')->deleteDirectory((string) $eleve->id);
}
DB::transaction(fn () => $classe->delete());
CacheTableauDeBordProf::vider($classe->user_id);
Log::info('Purge RGPD : classe supprimée', [
'classe_id' => $classe->id,
'eleves' => $classe->eleves_count,
'archivee_a' => $classe->archivee_a->toDateString(),
]);
$purgees++;
}
$this->info("{$purgees} classe(s) purgée(s).");
}
}