permet aux élèves de rejoindre leur classe avec son code, et les fiches en lot
L'élève saisit le code de classe et son prénom, le prof accepte, l'élève voit une seule fois son identifiant et son code. Aucun e-mail. Le prof peut aussi régénérer les fiches de plusieurs élèves d'un coup. Procédure pour prouver la copie de la clé des sauvegardes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1ac8d4773a
commit
dd4f9c3c55
21 changed files with 573 additions and 26 deletions
|
|
@ -3,9 +3,11 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Classe;
|
||||
use App\Models\DemandeInscription;
|
||||
use App\Models\Eleve;
|
||||
use App\Models\Seance;
|
||||
use App\Services\GenerateurCompteEleve;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -30,6 +32,7 @@ class ClasseController extends Controller
|
|||
|
||||
return view('mes-classes.show', [
|
||||
'classe' => $classe->load('eleves'),
|
||||
'demandes' => $classe->demandesInscription()->where('statut', DemandeInscription::EN_ATTENTE)->oldest()->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -104,25 +107,64 @@ class ClasseController extends Controller
|
|||
*/
|
||||
private function creerEleve(Classe $classe, string $prenom, ?string $nom): array
|
||||
{
|
||||
$identifiant = $this->generateur->genererIdentifiant($prenom, $nom);
|
||||
$codeSecret = $this->generateur->genererCodeSecret();
|
||||
$jetonQr = Str::random(32);
|
||||
$fiche = $this->generateur->creerDans($classe, $prenom, $nom);
|
||||
unset($fiche['eleve']);
|
||||
|
||||
$classe->eleves()->create([
|
||||
'prenom' => $prenom,
|
||||
'nom' => $nom,
|
||||
'identifiant' => $identifiant,
|
||||
'code_secret' => Hash::make($codeSecret),
|
||||
'jeton_qr' => $jetonQr,
|
||||
]);
|
||||
return $fiche;
|
||||
}
|
||||
|
||||
return [
|
||||
'identifiant' => $identifiant,
|
||||
'prenom' => $prenom,
|
||||
'nom' => $nom,
|
||||
'code_secret' => $codeSecret,
|
||||
'jeton_qr' => $jetonQr,
|
||||
];
|
||||
public function accepterDemande(Classe $classe, DemandeInscription $demande): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
abort_if($demande->classe_id !== $classe->id, 404);
|
||||
|
||||
if ($demande->statut === DemandeInscription::EN_ATTENTE && ! $classe->estArchivee()) {
|
||||
$fiche = $this->generateur->creerDans($classe, $demande->prenom, $demande->nom);
|
||||
$demande->forceFill([
|
||||
'statut' => DemandeInscription::ACCEPTEE,
|
||||
'eleve_id' => $fiche['eleve']->id,
|
||||
'code_secret_chiffre' => $fiche['code_secret'],
|
||||
])->save();
|
||||
}
|
||||
|
||||
return redirect()->route('mes-classes.show', $classe);
|
||||
}
|
||||
|
||||
public function refuserDemande(Classe $classe, DemandeInscription $demande): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
abort_if($demande->classe_id !== $classe->id, 404);
|
||||
|
||||
$demande->forceFill(['statut' => DemandeInscription::REFUSEE])->save();
|
||||
|
||||
return redirect()->route('mes-classes.show', $classe);
|
||||
}
|
||||
|
||||
public function nombreDemandes(Classe $classe): JsonResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
|
||||
return response()->json(['en_attente' => $classe->demandesInscription()->where('statut', DemandeInscription::EN_ATTENTE)->count()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Nouvelles fiches (nouveau code, nouveau QR) pour les élèves cochés :
|
||||
* la fiche n'est visible qu'une fois, ceci permet de la refaire en lot.
|
||||
*/
|
||||
public function nouvellesFiches(Request $request, Classe $classe): RedirectResponse
|
||||
{
|
||||
$this->verifierProprietaire($classe);
|
||||
$ids = $request->validate(['eleves' => ['required', 'array'], 'eleves.*' => ['integer']])['eleves'];
|
||||
|
||||
$fiches = [];
|
||||
foreach ($classe->eleves()->whereIn('id', $ids)->orderBy('prenom')->get() as $eleve) {
|
||||
$codeSecret = $this->generateur->genererCodeSecret();
|
||||
$jetonQr = Str::random(32);
|
||||
$eleve->forceFill(['code_secret' => Hash::make($codeSecret), 'jeton_qr' => $jetonQr])->save();
|
||||
$fiches[] = ['identifiant' => $eleve->identifiant, 'prenom' => $eleve->prenom, 'nom' => $eleve->nom, 'code_secret' => $codeSecret, 'jeton_qr' => $jetonQr];
|
||||
}
|
||||
|
||||
return redirect()->route('mes-classes.show', $classe)->with('fiches', $fiches);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
115
portail/app/Http/Controllers/RejoindreClasseController.php
Normal file
115
portail/app/Http/Controllers/RejoindreClasseController.php
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Classe;
|
||||
use App\Models\DemandeInscription;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\View\View;
|
||||
|
||||
/**
|
||||
* Un élève rejoint une classe avec son code (façon n°1 du CLAUDE.md) :
|
||||
* il donne son prénom, le prof accepte, l'élève voit une fois son
|
||||
* identifiant et son code secret, et il est connecté. Aucun e-mail.
|
||||
*/
|
||||
class RejoindreClasseController extends Controller
|
||||
{
|
||||
private const CLE_SESSION = 'demande_inscription_jeton';
|
||||
|
||||
public function formulaire(Request $request): View
|
||||
{
|
||||
return view('rejoindre.formulaire', ['code' => Str::upper((string) $request->query('code', ''))]);
|
||||
}
|
||||
|
||||
public function demander(Request $request): RedirectResponse
|
||||
{
|
||||
$donnees = $request->validate([
|
||||
'code' => ['required', 'string', 'size:6'],
|
||||
'prenom' => ['required', 'string', 'max:100'],
|
||||
'nom' => ['nullable', 'string', 'max:100'],
|
||||
]);
|
||||
|
||||
$classe = Classe::where('code_classe', Str::upper($donnees['code']))->first();
|
||||
|
||||
if ($classe === null || $classe->estArchivee()) {
|
||||
return back()->withInput()->withErrors(['code' => 'Ce code ne correspond à aucune classe ouverte. Vérifie-le auprès de ton prof.']);
|
||||
}
|
||||
|
||||
$jeton = Str::random(40);
|
||||
DemandeInscription::create([
|
||||
'classe_id' => $classe->id,
|
||||
'prenom' => trim($donnees['prenom']),
|
||||
'nom' => isset($donnees['nom']) ? trim($donnees['nom']) : null,
|
||||
'jeton_empreinte' => DemandeInscription::empreinte($jeton),
|
||||
'statut' => DemandeInscription::EN_ATTENTE,
|
||||
]);
|
||||
$request->session()->put(self::CLE_SESSION, $jeton);
|
||||
|
||||
return redirect()->route('rejoindre.attente');
|
||||
}
|
||||
|
||||
public function attente(Request $request): View|RedirectResponse
|
||||
{
|
||||
$demande = $this->demandeDeCeNavigateur($request);
|
||||
|
||||
if ($demande === null) {
|
||||
return redirect()->route('rejoindre');
|
||||
}
|
||||
|
||||
if ($demande->statut === DemandeInscription::ACCEPTEE) {
|
||||
return redirect()->route('rejoindre.bienvenue');
|
||||
}
|
||||
|
||||
if ($demande->statut === DemandeInscription::REFUSEE) {
|
||||
$demande->delete();
|
||||
$request->session()->forget(self::CLE_SESSION);
|
||||
|
||||
return view('rejoindre.refusee');
|
||||
}
|
||||
|
||||
return view('rejoindre.attente', ['demande' => $demande->load('classe')]);
|
||||
}
|
||||
|
||||
public function etat(Request $request): JsonResponse
|
||||
{
|
||||
return response()->json(['statut' => $this->demandeDeCeNavigateur($request)?->statut]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Le code secret n'est montré qu'une fois : la demande (et le code en
|
||||
* clair qu'elle porte) est supprimée dès l'affichage, l'élève est connecté.
|
||||
*/
|
||||
public function bienvenue(Request $request): View|RedirectResponse
|
||||
{
|
||||
$demande = $this->demandeDeCeNavigateur($request);
|
||||
|
||||
if ($demande === null || $demande->statut !== DemandeInscription::ACCEPTEE || $demande->eleve === null) {
|
||||
return redirect()->route(Auth::guard('eleve')->check() ? 'eleve' : 'rejoindre');
|
||||
}
|
||||
|
||||
$eleve = $demande->eleve;
|
||||
$codeSecret = $demande->code_secret_chiffre;
|
||||
$demande->delete();
|
||||
$request->session()->forget(self::CLE_SESSION);
|
||||
|
||||
Auth::guard('eleve')->login($eleve);
|
||||
$request->session()->regenerate();
|
||||
$eleve->forceFill(['jeton_qr' => null, 'derniere_connexion_a' => now()])->save();
|
||||
$request->session()->put('jeton_session_eleve', $eleve->demarreNouvelleSession());
|
||||
|
||||
return view('rejoindre.bienvenue', ['eleve' => $eleve, 'codeSecret' => $codeSecret]);
|
||||
}
|
||||
|
||||
private function demandeDeCeNavigateur(Request $request): ?DemandeInscription
|
||||
{
|
||||
$jeton = $request->session()->get(self::CLE_SESSION);
|
||||
|
||||
return is_string($jeton)
|
||||
? DemandeInscription::where('jeton_empreinte', DemandeInscription::empreinte($jeton))->first()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +82,7 @@ class PolitiqueSecuriteContenu
|
|||
return false;
|
||||
}
|
||||
|
||||
if (in_array($route->getName(), ['login', 'register'], true)) {
|
||||
if (in_array($route->getName(), ['login', 'register'], true) || str_starts_with((string) $route->getName(), 'rejoindre')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue