diff --git a/app/Http/Controllers/Api/MeetupController.php b/app/Http/Controllers/Api/MeetupController.php index c0dda4b..eb068b8 100644 --- a/app/Http/Controllers/Api/MeetupController.php +++ b/app/Http/Controllers/Api/MeetupController.php @@ -106,14 +106,15 @@ class MeetupController extends Controller /** * Eigene Meetups auflisten * - * Liefert alle vom authentifizierten Nutzer erstellten Meetups, alphabetisch sortiert. + * Liefert die im Dashboard ausgewählten Meetups des authentifizierten Nutzers + * (meetup_user-Pivot, „Meine Meetups"), alphabetisch sortiert. */ public function mine(Request $request): AnonymousResourceCollection { Gate::authorize('viewAny', Meetup::class); - $meetups = Meetup::query() - ->where('created_by', $request->user()->id) + $meetups = $request->user() + ->meetups() ->orderBy('name') ->get(); @@ -123,12 +124,12 @@ class MeetupController extends Controller /** * Eigenes Meetup anzeigen * - * Zeigt ein einzelnes, vom authentifizierten Nutzer erstelltes Meetup. + * Zeigt ein einzelnes der im Dashboard ausgewählten Meetups (meetup_user-Pivot). */ - #[Response(status: 403, description: 'Nur der Ersteller oder ein Super-Admin darf das Meetup sehen.')] + #[Response(status: 403, description: 'Nur der Ersteller oder ein Mitglied (meetup_user-Pivot) darf das Meetup sehen.')] public function mineShow(Meetup $meetup): MeetupResource { - Gate::authorize('view', $meetup); + Gate::authorize('viewMine', $meetup); return MeetupResource::make($meetup); } diff --git a/app/Policies/MeetupPolicy.php b/app/Policies/MeetupPolicy.php index 1c5471c..5a2948a 100644 --- a/app/Policies/MeetupPolicy.php +++ b/app/Policies/MeetupPolicy.php @@ -20,6 +20,17 @@ class MeetupPolicy return $this->owns($user, $meetup); } + /** + * Sichtbarkeit der „Meine Meetups"-Detailansicht: Neben dem Ersteller darf + * jedes Mitglied der meetup_user-Pivot („Meine Meetups" im Dashboard) das + * abonnierte Meetup über die REST-API ansehen. Spiegelt die Listen-Semantik + * von MeetupController::mine(), die ebenfalls die Pivot-Mitgliedschaft nutzt. + */ + public function viewMine(User $user, Meetup $meetup): bool + { + return $this->owns($user, $meetup) || $meetup->hasMember($user); + } + public function create(User $user): bool { return true; diff --git a/tests/Feature/Api/MeetupWriteApiTest.php b/tests/Feature/Api/MeetupWriteApiTest.php index e434c71..b3d1392 100644 --- a/tests/Feature/Api/MeetupWriteApiTest.php +++ b/tests/Feature/Api/MeetupWriteApiTest.php @@ -70,23 +70,36 @@ it('forbids updating as a pivot member who is not the creator', function () { ])->assertForbidden(); }); -it('returns only own in mine index', function () { +it('returns the dashboard-selected meetups in mine index', function () { Sanctum::actingAs($user = User::factory()->create()); - $other = User::factory()->create(); - Meetup::factory()->count(2)->create(['created_by' => $user->id]); - Meetup::factory()->create(['created_by' => $other->id]); + $selected = Meetup::factory()->count(2)->create(); + $unselected = Meetup::factory()->create(); + + $user->meetups()->attach($selected); $response = $this->getJson('/api/my-meetups'); $response->assertSuccessful(); expect($response->json('data'))->toHaveCount(2); - collect($response->json('data'))->each( - fn ($meetup) => expect($meetup['created_by'])->toBe($user->id) - ); + + $ids = collect($response->json('data'))->pluck('id')->all(); + expect($ids) + ->toContain(...$selected->pluck('id')->all()) + ->not->toContain($unselected->id); }); -it('forbids viewing someone elses in mine show', function () { +it('lets a pivot member view in mine show', function () { + Sanctum::actingAs($user = User::factory()->create()); + $meetup = Meetup::factory()->create(); + $user->meetups()->attach($meetup); + + $this->getJson('/api/my-meetups/'.$meetup->id) + ->assertSuccessful() + ->assertJsonPath('data.id', $meetup->id); +}); + +it('forbids viewing a meetup the user has not selected in mine show', function () { $owner = User::factory()->create(); $meetup = Meetup::factory()->create(['created_by' => $owner->id]);