diff --git a/api/app/Http/Controllers/Api/V1/MeController.php b/api/app/Http/Controllers/Api/V1/MeController.php index c9b46d87..70a61f6c 100644 --- a/api/app/Http/Controllers/Api/V1/MeController.php +++ b/api/app/Http/Controllers/Api/V1/MeController.php @@ -13,7 +13,12 @@ final class MeController extends Controller { public function __invoke(Request $request): JsonResponse { - $user = $request->user()->load(['organisations', 'roles', 'permissions']); + $user = $request->user()->load([ + 'organisations', + 'roles', + 'permissions', + 'persons' => fn ($q) => $q->with(['event:id,name,slug,start_date,end_date,organisation_id', 'event.organisation:id,name']), + ]); return $this->success(new MeResource($user)); } diff --git a/api/app/Http/Resources/Api/V1/MeResource.php b/api/app/Http/Resources/Api/V1/MeResource.php index 9ea92d86..485f834b 100644 --- a/api/app/Http/Resources/Api/V1/MeResource.php +++ b/api/app/Http/Resources/Api/V1/MeResource.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace App\Http\Resources\Api\V1; +use App\Models\Person; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; @@ -31,6 +32,18 @@ final class MeResource extends JsonResource ), 'app_roles' => $this->getRoleNames()->values()->all(), 'permissions' => $this->getAllPermissions()->pluck('name')->values()->all(), + 'portal_events' => $this->whenLoaded('persons', fn () => + $this->persons->map(fn (Person $person) => [ + 'event_id' => $person->event_id, + 'event_name' => $person->event->name, + 'event_slug' => $person->event->slug, + 'organisation_name' => $person->event->organisation->name, + 'person_id' => $person->id, + 'person_status' => $person->status, + 'start_date' => $person->event->start_date?->toDateString(), + 'end_date' => $person->event->end_date?->toDateString(), + ]) + ), ]; } } diff --git a/api/app/Models/User.php b/api/app/Models/User.php index 1060da89..61ab42aa 100644 --- a/api/app/Models/User.php +++ b/api/app/Models/User.php @@ -80,6 +80,11 @@ final class User extends Authenticatable return $this->hasMany(PersonIdentityMatch::class, 'matched_user_id'); } + public function persons(): HasMany + { + return $this->hasMany(Person::class, 'user_id'); + } + public function organisationTags(): HasMany { return $this->hasMany(UserOrganisationTag::class); diff --git a/api/tests/Feature/Api/V1/MePortalEventsTest.php b/api/tests/Feature/Api/V1/MePortalEventsTest.php new file mode 100644 index 00000000..4694e0b5 --- /dev/null +++ b/api/tests/Feature/Api/V1/MePortalEventsTest.php @@ -0,0 +1,163 @@ +seed(RoleSeeder::class); + + $this->organisation = Organisation::factory()->create(); + $this->volunteerCrowdType = CrowdType::factory()->systemType('VOLUNTEER')->create([ + 'organisation_id' => $this->organisation->id, + ]); + $this->event = Event::factory()->create([ + 'organisation_id' => $this->organisation->id, + 'status' => 'published', + ]); + } + + public function test_auth_me_includes_portal_events(): void + { + $user = User::factory()->create(); + + Person::factory()->approved()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $user->id, + 'email' => $user->email, + ]); + + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertOk(); + $response->assertJsonCount(1, 'data.portal_events'); + $response->assertJsonPath('data.portal_events.0.event_id', $this->event->id); + $response->assertJsonPath('data.portal_events.0.event_name', $this->event->name); + $response->assertJsonPath('data.portal_events.0.event_slug', $this->event->slug); + $response->assertJsonPath('data.portal_events.0.organisation_name', $this->organisation->name); + $response->assertJsonPath('data.portal_events.0.person_status', 'approved'); + } + + public function test_portal_events_contains_correct_event_dates(): void + { + $user = User::factory()->create(); + + Person::factory()->approved()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $user->id, + 'email' => $user->email, + ]); + + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertOk(); + $response->assertJsonPath('data.portal_events.0.start_date', $this->event->start_date->toDateString()); + $response->assertJsonPath('data.portal_events.0.end_date', $this->event->end_date->toDateString()); + } + + public function test_portal_events_only_includes_own_events(): void + { + $user = User::factory()->create(); + $otherUser = User::factory()->create(); + + Person::factory()->approved()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $user->id, + 'email' => $user->email, + ]); + + // Other user's person record — should not appear + Person::factory()->approved()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $otherUser->id, + 'email' => $otherUser->email, + ]); + + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertOk(); + $response->assertJsonCount(1, 'data.portal_events'); + $response->assertJsonPath('data.portal_events.0.person_status', 'approved'); + } + + public function test_portal_events_empty_when_no_persons(): void + { + $user = User::factory()->create(); + + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertOk(); + $response->assertJsonCount(0, 'data.portal_events'); + } + + public function test_portal_events_includes_multiple_events(): void + { + $user = User::factory()->create(); + + $secondEvent = Event::factory()->create([ + 'organisation_id' => $this->organisation->id, + 'status' => 'published', + ]); + + Person::factory()->approved()->create([ + 'event_id' => $this->event->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $user->id, + 'email' => $user->email, + ]); + + Person::factory()->create([ + 'event_id' => $secondEvent->id, + 'crowd_type_id' => $this->volunteerCrowdType->id, + 'user_id' => $user->id, + 'email' => $user->email, + 'status' => 'pending', + ]); + + Sanctum::actingAs($user); + + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertOk(); + $response->assertJsonCount(2, 'data.portal_events'); + } + + public function test_unauthenticated_returns_401(): void + { + $response = $this->getJson('/api/v1/auth/me'); + + $response->assertStatus(401); + } +}