feat(api): add portal_events to auth/me endpoint

Add persons() relationship to User model and include portal_events
array in MeResource response, mapping each person record to its
event and organisation data for the portal frontend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 07:38:59 +02:00
parent 34eb790b3e
commit 7228ad9f5a
4 changed files with 187 additions and 1 deletions

View File

@@ -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));
}

View File

@@ -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(),
])
),
];
}
}

View File

@@ -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);

View File

@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\Api\V1;
use App\Models\CrowdType;
use App\Models\Event;
use App\Models\Organisation;
use App\Models\Person;
use App\Models\User;
use Database\Seeders\RoleSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;
class MePortalEventsTest extends TestCase
{
use RefreshDatabase;
private Organisation $organisation;
private Event $event;
private CrowdType $volunteerCrowdType;
protected function setUp(): void
{
parent::setUp();
$this->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);
}
}