diff --git a/app/Http/Controllers/Admin/SubscriberController.php b/app/Http/Controllers/Admin/SubscriberController.php index 6d7a663..9e87f4d 100644 --- a/app/Http/Controllers/Admin/SubscriberController.php +++ b/app/Http/Controllers/Admin/SubscriberController.php @@ -5,9 +5,11 @@ declare(strict_types=1); namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Http\Requests\Admin\DestroySubscriberRequest; use App\Http\Requests\Admin\IndexSubscriberRequest; use App\Http\Requests\Admin\QueueMailwizzSyncRequest; use App\Models\PreregistrationPage; +use App\Models\Subscriber; use App\Services\DispatchUnsyncedMailwizzSyncJobsService; use Illuminate\Http\RedirectResponse; use Illuminate\View\View; @@ -32,6 +34,15 @@ class SubscriberController extends Controller return view('admin.subscribers.index', compact('page', 'subscribers', 'unsyncedMailwizzCount')); } + public function destroy(DestroySubscriberRequest $request, PreregistrationPage $page, Subscriber $subscriber): RedirectResponse + { + $subscriber->delete(); + + return redirect() + ->route('admin.pages.subscribers.index', $page) + ->with('status', __('Subscriber removed.')); + } + public function queueMailwizzSync( QueueMailwizzSyncRequest $request, PreregistrationPage $page, diff --git a/app/Http/Requests/Admin/DestroySubscriberRequest.php b/app/Http/Requests/Admin/DestroySubscriberRequest.php new file mode 100644 index 0000000..10f7a1c --- /dev/null +++ b/app/Http/Requests/Admin/DestroySubscriberRequest.php @@ -0,0 +1,36 @@ +route('page'); + $subscriber = $this->route('subscriber'); + if (! $page instanceof PreregistrationPage || ! $subscriber instanceof Subscriber) { + return false; + } + + if ($subscriber->preregistration_page_id !== $page->id) { + return false; + } + + return $this->user()?->can('update', $page) ?? false; + } + + /** + * @return array> + */ + public function rules(): array + { + return []; + } +} diff --git a/lang/nl.json b/lang/nl.json index d3a3a45..54c9fb0 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -18,5 +18,9 @@ "Thank you for registering!": "Bedankt voor je registratie!", "You are already registered for this event.": "Je bent al geregistreerd voor dit evenement.", "Please enter a valid email address.": "Voer een geldig e-mailadres in.", - "Please enter a valid phone number (8–15 digits).": "Voer een geldig telefoonnummer in (8 tot 15 cijfers)." + "Please enter a valid phone number (8–15 digits).": "Voer een geldig telefoonnummer in (8 tot 15 cijfers).", + "Subscriber removed.": "Abonnee verwijderd.", + "Delete this subscriber? This cannot be undone.": "Deze abonnee verwijderen? Dit kan niet ongedaan worden gemaakt.", + "Remove": "Verwijderen", + "Actions": "Acties" } diff --git a/resources/views/admin/subscribers/index.blade.php b/resources/views/admin/subscribers/index.blade.php index aecf2ba..15a923c 100644 --- a/resources/views/admin/subscribers/index.blade.php +++ b/resources/views/admin/subscribers/index.blade.php @@ -54,6 +54,7 @@ @endif {{ __('Registered at') }} {{ __('Mailwizz') }} + {{ __('Actions') }} @@ -77,10 +78,29 @@ @endif + + @can('update', $page) +
+ @csrf + @method('DELETE') + +
+ @endcan + @empty - + {{ __('No subscribers match your criteria.') }} diff --git a/routes/web.php b/routes/web.php index 8dadd59..2951594 100644 --- a/routes/web.php +++ b/routes/web.php @@ -33,6 +33,7 @@ Route::middleware(['auth', 'verified'])->prefix('admin')->name('admin.')->group( // Subscribers (nested under pages) — export before index so the path is unambiguous Route::get('pages/{page}/subscribers/export', [SubscriberController::class, 'export'])->name('pages.subscribers.export'); + Route::delete('pages/{page}/subscribers/{subscriber}', [SubscriberController::class, 'destroy'])->name('pages.subscribers.destroy'); Route::post('pages/{page}/subscribers/queue-mailwizz-sync', [SubscriberController::class, 'queueMailwizzSync'])->name('pages.subscribers.queue-mailwizz-sync'); Route::get('pages/{page}/subscribers', [SubscriberController::class, 'index'])->name('pages.subscribers.index'); diff --git a/tests/Feature/DestroySubscriberTest.php b/tests/Feature/DestroySubscriberTest.php new file mode 100644 index 0000000..2ed8abe --- /dev/null +++ b/tests/Feature/DestroySubscriberTest.php @@ -0,0 +1,131 @@ +create(['role' => 'user']); + $page = PreregistrationPage::query()->create([ + 'slug' => (string) Str::uuid(), + 'user_id' => $user->id, + 'title' => 'Fest', + 'heading' => 'Fest', + 'intro_text' => null, + 'thank_you_message' => null, + 'expired_message' => null, + 'ticketshop_url' => null, + 'start_date' => now()->subDay(), + 'end_date' => now()->addMonth(), + 'phone_enabled' => false, + 'background_image' => null, + 'logo_image' => null, + 'is_active' => true, + ]); + $subscriber = Subscriber::query()->create([ + 'preregistration_page_id' => $page->id, + 'first_name' => 'Ada', + 'last_name' => 'Lovelace', + 'email' => 'ada@example.com', + ]); + + $response = $this->actingAs($user)->delete(route('admin.pages.subscribers.destroy', [$page, $subscriber])); + + $response->assertRedirect(route('admin.pages.subscribers.index', $page)); + $response->assertSessionHas('status'); + $this->assertDatabaseMissing('subscribers', ['id' => $subscriber->id]); + } + + public function test_other_user_cannot_delete_subscriber(): void + { + $owner = User::factory()->create(['role' => 'user']); + $intruder = User::factory()->create(['role' => 'user']); + $page = PreregistrationPage::query()->create([ + 'slug' => (string) Str::uuid(), + 'user_id' => $owner->id, + 'title' => 'Fest', + 'heading' => 'Fest', + 'intro_text' => null, + 'thank_you_message' => null, + 'expired_message' => null, + 'ticketshop_url' => null, + 'start_date' => now()->subDay(), + 'end_date' => now()->addMonth(), + 'phone_enabled' => false, + 'background_image' => null, + 'logo_image' => null, + 'is_active' => true, + ]); + $subscriber = Subscriber::query()->create([ + 'preregistration_page_id' => $page->id, + 'first_name' => 'A', + 'last_name' => 'B', + 'email' => 'x@example.com', + ]); + + $response = $this->actingAs($intruder)->delete(route('admin.pages.subscribers.destroy', [$page, $subscriber])); + + $response->assertForbidden(); + $this->assertDatabaseHas('subscribers', ['id' => $subscriber->id]); + } + + public function test_cannot_delete_subscriber_using_wrong_page_in_url(): void + { + $user = User::factory()->create(['role' => 'user']); + $pageA = PreregistrationPage::query()->create([ + 'slug' => (string) Str::uuid(), + 'user_id' => $user->id, + 'title' => 'A', + 'heading' => 'A', + 'intro_text' => null, + 'thank_you_message' => null, + 'expired_message' => null, + 'ticketshop_url' => null, + 'start_date' => now()->subDay(), + 'end_date' => now()->addMonth(), + 'phone_enabled' => false, + 'background_image' => null, + 'logo_image' => null, + 'is_active' => true, + ]); + $pageB = PreregistrationPage::query()->create([ + 'slug' => (string) Str::uuid(), + 'user_id' => $user->id, + 'title' => 'B', + 'heading' => 'B', + 'intro_text' => null, + 'thank_you_message' => null, + 'expired_message' => null, + 'ticketshop_url' => null, + 'start_date' => now()->subDay(), + 'end_date' => now()->addMonth(), + 'phone_enabled' => false, + 'background_image' => null, + 'logo_image' => null, + 'is_active' => true, + ]); + $subscriber = Subscriber::query()->create([ + 'preregistration_page_id' => $pageB->id, + 'first_name' => 'A', + 'last_name' => 'B', + 'email' => 'y@example.com', + ]); + + $response = $this->actingAs($user)->delete(route('admin.pages.subscribers.destroy', [$pageA, $subscriber])); + + $response->assertForbidden(); + $this->assertDatabaseHas('subscribers', ['id' => $subscriber->id]); + } +}