Paillasse/portail/app/Http/Controllers/SeanceController.php

189 lines
6.8 KiB
PHP
Raw Permalink Normal View History

<?php
namespace App\Http\Controllers;
use App\Jobs\CreerStackLab;
use App\Models\Classe;
use App\Models\LabSession;
use App\Models\Seance;
use App\Services\Labs\StockChaud;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
class SeanceController extends Controller
{
/**
* Sans date : la salle d'attente s'ouvre tout de suite. Avec une date :
* séance planifiée, le stock chaud du lab se remplit 10 minutes avant.
*/
public function store(Request $request, Classe $classe): RedirectResponse
{
abort_if($classe->user_id !== Auth::guard('web')->id(), 403);
if ($classe->estArchivee()) {
return back()->withErrors(['prevue_a' => 'Cette classe est archivée : désarchive-la pour lancer une séance.']);
}
$donnees = $request->validate([
'lab_id' => ['required', 'exists:labs,id'],
'prevue_a' => ['nullable', 'date_format:Y-m-d\TH:i'],
'mode_examen' => ['nullable', 'boolean'],
'duree_examen_min' => ['required_if:mode_examen,1', 'nullable', 'integer', 'min:5', 'max:300'],
]);
$modeExamen = $request->boolean('mode_examen');
if (! empty($donnees['prevue_a'])) {
$prevueA = Carbon::createFromFormat('Y-m-d\TH:i', $donnees['prevue_a'], config('app.fuseau_utilisateurs'))->utc();
if ($prevueA->isPast()) {
return back()->withErrors(['prevue_a' => 'Choisis une date à venir.'])->withInput();
}
$classe->seances()->create([
'lab_id' => $donnees['lab_id'],
'statut' => Seance::PLANIFIEE,
'prevue_a' => $prevueA,
'mode_examen' => $modeExamen,
'duree_examen_min' => $modeExamen ? $donnees['duree_examen_min'] : null,
]);
return redirect()->route('mes-classes.show', $classe)->with('seance_planifiee', true);
}
$seance = $classe->seances()->create([
'lab_id' => $donnees['lab_id'],
'statut' => Seance::SALLE_ATTENTE,
'demarree_a' => now(),
'mode_examen' => $modeExamen,
'duree_examen_min' => $modeExamen ? $donnees['duree_examen_min'] : null,
]);
return redirect()->route('seances.attente', $seance);
}
public function ouvrir(Seance $seance): RedirectResponse
{
abort_if($seance->classe->user_id !== Auth::guard('web')->id(), 403);
if ($seance->statut === Seance::PLANIFIEE) {
$seance->update(['statut' => Seance::SALLE_ATTENTE, 'demarree_a' => now()]);
}
return redirect()->route('seances.attente', $seance);
}
public function attente(Seance $seance): View
{
abort_if($seance->classe->user_id !== Auth::guard('web')->id(), 403);
return view('seances.attente', [
'seance' => $seance->load(['classe', 'lab', 'labSessions.eleve', 'labSessions.machines', 'labSessions.derniereCorrection', 'labSessions.evenementsExamen']),
]);
}
public function consoleProf(Seance $seance, LabSession $labSession): View
{
abort_if($seance->classe->user_id !== Auth::guard('web')->id(), 403);
abort_if($labSession->seance_id !== $seance->id, 404);
return view('seances.console-prof', [
'seance' => $seance->load('lab'),
'labSession' => $labSession->load(['eleve', 'machines']),
]);
}
/**
* Admettre un élève, c'est lancer son lab. Chemin rapide : une stack
* prête est prise dans le stock chaud et attribuée tout de suite.
* Chemin lent (stock vide) : CreerStackLab la construit (20 à 40 s).
* Aussi utilisé pour relancer une session en échec.
*/
public function admettre(Seance $seance, LabSession $labSession, StockChaud $stock): RedirectResponse
{
abort_if($seance->classe->user_id !== Auth::guard('web')->id(), 403);
abort_if($labSession->seance_id !== $seance->id, 404);
// Mise à jour conditionnelle en une seule requête SQL : un double clic
// (ou deux onglets) ne peut pas lancer deux stacks pour le même élève.
$pris = LabSession::whereKey($labSession->id)
->whereIn('etat', [LabSession::EN_ATTENTE, LabSession::ECHEC])
->update([
'etat' => LabSession::ADMIS,
'raison_echec' => null,
'expire_a' => now()->addMinutes($seance->mode_examen ? $seance->duree_examen_min : ($labSession->lab->duree_min ?? 45)),
]);
if ($pris === 0) {
return redirect()->route('seances.attente', $seance);
}
$labSession->refresh();
$pret = $stock->prendre($labSession->lab->identifiant);
if ($pret !== null) {
$labSession->attribuerStack($pret);
$stock->confirmerAttribution($pret);
} else {
CreerStackLab::dispatch($labSession);
}
return redirect()->route('seances.attente', $seance);
}
/**
* Un élève rejoint la séance : il patiente jusqu'à ce que le prof l'admette.
*/
public function rejoindre(Request $request, Seance $seance): RedirectResponse
{
$eleve = Auth::guard('eleve')->user();
abort_if($eleve->classe_id !== $seance->classe_id, 403);
abort_if($seance->classe->estArchivee(), 403);
LabSession::firstOrCreate(
['seance_id' => $seance->id, 'eleve_id' => $eleve->id],
['lab_id' => $seance->lab_id, 'etat' => LabSession::EN_ATTENTE],
);
return redirect()->route('seances.eleve.attente', $seance);
}
public function attenteEleve(Seance $seance): View|RedirectResponse
{
$labSession = $this->sessionDeLEleve($seance);
if ($labSession === null) {
return redirect()->route('eleve');
}
return view('seances.attente-eleve', [
'seance' => $seance->load('lab'),
'labSession' => $labSession->load(['machines', 'derniereCorrection']),
]);
}
/**
* Interrogé chaque seconde par la page d'attente de l'élève : la page se
* recharge dès que l'état change (au lieu d'un rafraîchissement toutes
* les 5 s, incompatible avec un lab ouvert en moins de 5 s).
*/
public function etatEleve(Seance $seance): JsonResponse
{
return response()->json(['etat' => $this->sessionDeLEleve($seance)?->etat]);
}
private function sessionDeLEleve(Seance $seance): ?LabSession
{
$eleve = Auth::guard('eleve')->user();
abort_if($eleve->classe_id !== $seance->classe_id, 403);
return LabSession::where('seance_id', $seance->id)
->where('eleve_id', $eleve->id)
->first();
}
}