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:
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Recording;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class CancelTranscriptionController extends Controller
|
||||
{
|
||||
@@ -12,6 +13,8 @@ class CancelTranscriptionController extends Controller
|
||||
*/
|
||||
public function __invoke(Recording $recording): RedirectResponse
|
||||
{
|
||||
Gate::authorize('transcribe', $recording);
|
||||
|
||||
if (! $recording->isTranscribing()) {
|
||||
return back()->with('error', 'No transcription is currently running.');
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Http\Controllers;
|
||||
use App\Http\Requests\TranscribeRecordingRequest;
|
||||
use App\Models\Recording;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class TranscribeController extends Controller
|
||||
{
|
||||
@@ -15,6 +16,8 @@ class TranscribeController extends Controller
|
||||
*/
|
||||
public function __invoke(TranscribeRecordingRequest $request, Recording $recording): RedirectResponse
|
||||
{
|
||||
Gate::authorize('transcribe', $recording);
|
||||
|
||||
$recording->queueLocalTranscription();
|
||||
|
||||
return back()->with('success', 'Transcription started. Progress updates below.');
|
||||
|
||||
@@ -4,17 +4,18 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Recording;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class TranscribePendingController extends Controller
|
||||
{
|
||||
/**
|
||||
* Queue local transcription for recordings that still need a transcript.
|
||||
*/
|
||||
public function __invoke(): RedirectResponse
|
||||
public function __invoke(Request $request): RedirectResponse
|
||||
{
|
||||
$queued = 0;
|
||||
|
||||
Recording::query()
|
||||
$request->user()->recordings()
|
||||
->whereIn('transcription_status', ['pending', 'failed', 'cancelled'])
|
||||
->orderBy('id')
|
||||
->each(function (Recording $recording) use (&$queued): void {
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Recording;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
class TranscriptionStatusController extends Controller
|
||||
{
|
||||
@@ -12,6 +13,8 @@ class TranscriptionStatusController extends Controller
|
||||
*/
|
||||
public function __invoke(Recording $recording): JsonResponse
|
||||
{
|
||||
Gate::authorize('view', $recording);
|
||||
|
||||
$recording = $recording->fresh();
|
||||
|
||||
if ($recording->recoverOrphanedTranscription()) {
|
||||
|
||||
Reference in New Issue
Block a user