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:
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
])
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
163
api/tests/Feature/Api/V1/MePortalEventsTest.php
Normal file
163
api/tests/Feature/Api/V1/MePortalEventsTest.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user