Dépôt de fichier en plus du texte libre pour les devoirs maison, stocké sur Hetzner Object Storage (jamais sur le disque du serveur), masqué côté élève tant que le bucket n'est pas configuré. Purge RGPD étendue pour supprimer aussi les fichiers d'Object Storage. Ansible lit les identifiants depuis un fichier que seul Adam peut créer sur le serveur (runbook docs/runbooks/stockage-s3.md) ; le bloc B du CLAUDE.md est maintenant complet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
96 lines
3.1 KiB
PHP
96 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Classe;
|
|
use App\Models\Devoir;
|
|
use App\Models\RenduDevoir;
|
|
use App\Services\Stockage\StockageTravauxEleves;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\View\View;
|
|
|
|
/**
|
|
* Devoirs maison (chantier « vrai cockpit », étape 3). Côté prof : créer,
|
|
* voir qui a rendu, noter. Côté élève : DevoirEleveController.
|
|
*/
|
|
class DevoirController extends Controller
|
|
{
|
|
public function index(Classe $classe): View
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
|
|
return view('devoirs.index', ['classe' => $classe, 'devoirs' => $classe->devoirs()->withCount('rendus')->get()]);
|
|
}
|
|
|
|
public function store(Request $request, Classe $classe): RedirectResponse
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
$donnees = $request->validate([
|
|
'titre' => ['required', 'string', 'max:150'],
|
|
'consigne' => ['required', 'string', 'max:5000'],
|
|
'echeance' => ['required', 'date', 'after:now'],
|
|
]);
|
|
|
|
$devoir = $classe->devoirs()->create($donnees);
|
|
|
|
return redirect()->route('mes-classes.devoirs.show', [$classe, $devoir]);
|
|
}
|
|
|
|
public function show(Classe $classe, Devoir $devoir): View
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
$this->verifierAppartenance($classe, $devoir);
|
|
|
|
$rendus = $devoir->rendus()->with('eleve')->get()->keyBy('eleve_id');
|
|
|
|
return view('devoirs.show', [
|
|
'classe' => $classe,
|
|
'devoir' => $devoir,
|
|
'eleves' => $classe->eleves,
|
|
'rendus' => $rendus,
|
|
]);
|
|
}
|
|
|
|
public function noter(Request $request, Classe $classe, Devoir $devoir, RenduDevoir $rendu): RedirectResponse
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
$this->verifierAppartenance($classe, $devoir);
|
|
abort_if($rendu->devoir_id !== $devoir->id, 404);
|
|
|
|
$donnees = $request->validate(['note' => ['required', 'integer', 'min:0', 'max:20']]);
|
|
$rendu->update($donnees);
|
|
|
|
return redirect()->route('mes-classes.devoirs.show', [$classe, $devoir]);
|
|
}
|
|
|
|
public function telechargerRendu(Classe $classe, Devoir $devoir, RenduDevoir $rendu): RedirectResponse
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
$this->verifierAppartenance($classe, $devoir);
|
|
abort_if($rendu->devoir_id !== $devoir->id, 404);
|
|
abort_if($rendu->fichier_chemin === null, 404);
|
|
|
|
return redirect()->away(StockageTravauxEleves::urlTemporaire($rendu->fichier_chemin));
|
|
}
|
|
|
|
public function destroy(Classe $classe, Devoir $devoir): RedirectResponse
|
|
{
|
|
$this->verifierProprietaire($classe);
|
|
$this->verifierAppartenance($classe, $devoir);
|
|
$devoir->delete();
|
|
|
|
return redirect()->route('mes-classes.devoirs.index', $classe);
|
|
}
|
|
|
|
private function verifierProprietaire(Classe $classe): void
|
|
{
|
|
abort_if($classe->user_id !== Auth::guard('web')->id(), 403);
|
|
}
|
|
|
|
private function verifierAppartenance(Classe $classe, Devoir $devoir): void
|
|
{
|
|
abort_if($devoir->classe_id !== $classe->id, 404);
|
|
}
|
|
}
|