diff --git a/app/Livewire/Recordings/Index.php b/app/Livewire/Recordings/Index.php index 89478ee..e003ca1 100644 --- a/app/Livewire/Recordings/Index.php +++ b/app/Livewire/Recordings/Index.php @@ -6,6 +6,7 @@ use App\Models\Recording; use Flux\Flux; use Illuminate\Contracts\View\View; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Gate; use Livewire\Attributes\Layout; use Livewire\Attributes\Title; use Livewire\Attributes\Url; @@ -56,6 +57,18 @@ class Index extends Component ); } + public function delete(int $recordingId): void + { + $recording = Auth::user()->recordings()->findOrFail($recordingId); + + Gate::authorize('delete', $recording); + + $recording->deleteFile(); + $recording->delete(); + + Flux::toast(text: 'Recording deleted.', variant: 'success'); + } + public function render(): View { $user = Auth::user(); diff --git a/resources/views/livewire/recordings/index.blade.php b/resources/views/livewire/recordings/index.blade.php index 3297a5e..a0fe5ba 100644 --- a/resources/views/livewire/recordings/index.blade.php +++ b/resources/views/livewire/recordings/index.blade.php @@ -85,6 +85,7 @@ Words Status Uploaded + @@ -152,6 +153,42 @@ {{ $recording->created_at?->format('Y-m-d H:i') }} + + + + + + + + +
+
+ Delete recording? + + This permanently removes “{{ $recording->title }}” and its audio file. + +
+
+ + Cancel + + + Delete + +
+
+
+
@endforeach
diff --git a/tests/Feature/Recordings/IndexTest.php b/tests/Feature/Recordings/IndexTest.php index c656b53..cff7242 100644 --- a/tests/Feature/Recordings/IndexTest.php +++ b/tests/Feature/Recordings/IndexTest.php @@ -6,8 +6,10 @@ use App\Jobs\TranscribeRecording; use App\Livewire\Recordings\Index; use App\Models\Recording; use App\Models\User; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Bus; +use Illuminate\Support\Facades\Storage; use Livewire\Livewire; use Tests\TestCase; @@ -87,4 +89,60 @@ class IndexTest extends TestCase Bus::assertDispatched(TranscribeRecording::class, 1); } + + public function test_user_can_delete_recording_from_index(): void + { + Storage::fake('local'); + Storage::disk('local')->put('recordings/delete-me.mp3', 'bytes'); + + $user = User::factory()->create(); + $this->actingAs($user); + + $recording = Recording::query()->create([ + 'user_id' => $user->id, + 'title' => 'Delete from list', + 'original_filename' => 'delete-me.mp3', + 'file_path' => 'recordings/delete-me.mp3', + 'file_size_bytes' => 5, + 'transcription_status' => 'done', + ]); + + Livewire::test(Index::class) + ->assertSee('Delete from list') + ->call('delete', $recording->id) + ->assertDontSee('Delete from list'); + + $this->assertDatabaseMissing('recordings', ['id' => $recording->id]); + Storage::disk('local')->assertMissing('recordings/delete-me.mp3'); + } + + public function test_user_cannot_delete_another_users_recording_from_index(): void + { + Storage::fake('local'); + + $owner = User::factory()->create(); + $intruder = User::factory()->create(); + + $recording = Recording::query()->create([ + 'user_id' => $owner->id, + 'title' => 'Keep me', + 'original_filename' => 'keep.mp3', + 'file_path' => 'recordings/keep.mp3', + 'file_size_bytes' => 10, + 'transcription_status' => 'done', + ]); + + $this->actingAs($intruder); + + try { + Livewire::test(Index::class) + ->call('delete', $recording->id); + + $this->fail('Expected deleting another user\'s recording to fail.'); + } catch (ModelNotFoundException) { + // Owned-query findOrFail hides other users' recordings. + } + + $this->assertDatabaseHas('recordings', ['id' => $recording->id]); + } }