Add Fortify auth, Flux dark mode, demo seed, and Docker hot reload.

Protect recordings per user, seed a demo login on container start, and bind-mount the app with Vite HMR for local Compose development.
This commit is contained in:
ben
2026-08-12 18:38:53 +02:00
parent accb721811
commit bfcfc12f58
78 changed files with 3942 additions and 186 deletions
+18 -8
View File
@@ -8,6 +8,7 @@ use App\Services\Mp3MetadataService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
@@ -18,12 +19,14 @@ class RecordingController extends Controller
*/
public function index(Request $request): View
{
Recording::query()
$user = $request->user();
$user->recordings()
->whereIn('transcription_status', ['pending', 'processing'])
->orderBy('id')
->each(fn (Recording $recording) => $recording->recoverOrphanedTranscription());
$query = Recording::query()->latest();
$query = $user->recordings()->latest();
if ($search = $request->string('q')->trim()->toString()) {
$query->search($search);
@@ -31,7 +34,7 @@ class RecordingController extends Controller
$recordings = $query->paginate(20)->withQueryString();
$pendingCount = Recording::query()
$pendingCount = $user->recordings()
->whereIn('transcription_status', ['pending', 'failed', 'cancelled'])
->get()
->filter(fn (Recording $recording) => ! $recording->hasActiveTranscriptionJob())
@@ -47,9 +50,9 @@ class RecordingController extends Controller
/**
* Show the upload form.
*/
public function create(): View
public function create(Request $request): View
{
$existingFingerprints = Recording::query()
$existingFingerprints = $request->user()->recordings()
->get(['original_filename', 'file_size_bytes'])
->map(fn (Recording $recording) => $this->uploadFingerprint(
$recording->original_filename,
@@ -75,6 +78,7 @@ class RecordingController extends Controller
fn ($file) => $file instanceof UploadedFile,
));
$user = $request->user();
$titleOverride = $request->string('title')->trim()->toString();
$recordings = [];
$skippedDuplicates = 0;
@@ -89,8 +93,8 @@ class RecordingController extends Controller
if (
isset($seenHashes[$hash])
|| Recording::query()->where('content_hash', $hash)->exists()
|| Recording::query()
|| $user->recordings()->where('content_hash', $hash)->exists()
|| $user->recordings()
->where('original_filename', $file->getClientOriginalName())
->where('file_size_bytes', $file->getSize() ?: 0)
->exists()
@@ -106,7 +110,7 @@ class RecordingController extends Controller
? $titleOverride
: null;
$recordings[] = $this->storeUploadedRecording($file, $metadata, $hash, $title);
$recordings[] = $this->storeUploadedRecording($request, $file, $metadata, $hash, $title);
}
if ($recordings === [] && $skippedDuplicates > 0) {
@@ -149,6 +153,8 @@ class RecordingController extends Controller
*/
public function show(Recording $recording): View
{
Gate::authorize('view', $recording);
$recording->recoverOrphanedTranscription();
$recording->refresh();
@@ -160,6 +166,8 @@ class RecordingController extends Controller
*/
public function destroy(Recording $recording): RedirectResponse
{
Gate::authorize('delete', $recording);
$recording->deleteFile();
$recording->delete();
@@ -172,6 +180,7 @@ class RecordingController extends Controller
* Persist a single uploaded audio file as a recording.
*/
private function storeUploadedRecording(
Request $request,
UploadedFile $file,
Mp3MetadataService $metadata,
string $contentHash,
@@ -185,6 +194,7 @@ class RecordingController extends Controller
?: ($tags['title'] ?? pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME));
$recording = Recording::create([
'user_id' => $request->user()->id,
'title' => $title,
'original_filename' => $file->getClientOriginalName(),
'file_path' => $path,