Skip to content

Commit cfb34dd

Browse files
committed
feat: add new endpoint to retrieve member by external id
GET /api/v1/members/external/{external_id}
1 parent 6439c0a commit cfb34dd

4 files changed

Lines changed: 154 additions & 1 deletion

File tree

app/Http/Controllers/Apis/Protected/Main/OAuth2MembersApiController.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,50 @@ public function addMyAffiliation()
501501
return $this->addAffiliation('me');
502502
}
503503

504+
#[OA\Get(
505+
path: '/api/v1/members/external/{external_id}',
506+
operationId: 'getMemberByIdExternalId',
507+
summary: 'Get member by external Id',
508+
description: 'Returns a member profile by External Id',
509+
tags: ['Members'],
510+
x: [
511+
'required-groups' => [
512+
IGroup::SuperAdmins,
513+
IGroup::Administrators,
514+
IGroup::SummitAdministrators,
515+
]
516+
],
517+
security: [['members_oauth2' => [
518+
MemberScopes::ReadMemberData,
519+
]]],
520+
parameters: [
521+
new OA\Parameter(name: 'external_id', in: 'path', required: true, description: 'Member External ID', schema: new OA\Schema(type: 'integer')),
522+
new OA\Parameter(name: 'expand', in: 'query', required: false, description: 'Expand relationships', schema: new OA\Schema(type: 'string')),
523+
],
524+
responses: [
525+
new OA\Response(
526+
response: Response::HTTP_OK,
527+
description: 'Successful operation',
528+
content: new OA\JsonContent(ref: '#/components/schemas/Member')
529+
),
530+
new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: 'Unauthorized'),
531+
new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Member not found'),
532+
]
533+
)]
534+
public function getMemberByIdExternalId($external_id){
535+
return $this->processRequest(function() use($external_id){
536+
$member = $this->repository->getByExternalId($external_id);
537+
if(is_null($member))
538+
throw new EntityNotFoundException("Member not found by external Id.");
539+
return $this->ok(SerializerRegistry::getInstance()->getSerializer($member, SerializerRegistry::SerializerType_Private)->serialize
540+
(
541+
SerializerUtils::getExpand(),
542+
SerializerUtils::getFields(),
543+
SerializerUtils::getRelations()
544+
));
545+
});
546+
}
547+
504548
#[OA\Post(
505549
path: '/api/v1/members/{member_id}/affiliations',
506550
operationId: 'addMemberAffiliation',
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php namespace Database\Migrations\Config;
2+
/**
3+
* Copyright 2026 OpenStack Foundation
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
**/
14+
15+
use Doctrine\Migrations\AbstractMigration;
16+
use Doctrine\DBAL\Schema\Schema;
17+
use App\Security\MemberScopes;
18+
use App\Models\Foundation\Main\IGroup;
19+
20+
/**
21+
* Migration to seed the get-member-by-external-id endpoint.
22+
*
23+
* Adds:
24+
* - 1 api_endpoints row (get-member-by-external-id)
25+
* - 1 endpoint_api_scopes association (ReadMemberData)
26+
* - 3 endpoint_api_authz_groups rows (super-admins, administrators, summit-front-end-administrators)
27+
*
28+
* All INSERTs are idempotent via WHERE NOT EXISTS.
29+
*/
30+
final class Version20260410172200 extends AbstractMigration
31+
{
32+
use APIEndpointsMigrationHelper;
33+
34+
private const API_NAME = 'members';
35+
private const ENDPOINT_NAME = 'get-member-by-external-id';
36+
private const ENDPOINT_ROUTE = '/api/v1/members/external/{external_id}';
37+
38+
public function getDescription(): string
39+
{
40+
return 'Seed get-member-by-external-id endpoint with scope and authz groups';
41+
}
42+
43+
/**
44+
* @param Schema $schema
45+
*/
46+
public function up(Schema $schema): void
47+
{
48+
$scope = MemberScopes::ReadMemberData;
49+
50+
// 1. Insert the endpoint
51+
$this->addSql($this->insertEndpoint(
52+
self::API_NAME,
53+
self::ENDPOINT_NAME,
54+
self::ENDPOINT_ROUTE,
55+
'GET'
56+
));
57+
58+
// 2. Insert endpoint_api_scopes association
59+
$this->addSql($this->insertEndpointScope(self::API_NAME, self::ENDPOINT_NAME, $scope));
60+
61+
// 3. Insert endpoint_api_authz_groups
62+
$authzGroups = [
63+
IGroup::SuperAdmins,
64+
IGroup::Administrators,
65+
IGroup::SummitAdministrators,
66+
];
67+
68+
foreach ($authzGroups as $groupSlug) {
69+
$this->addSql($this->insertEndpointAuthzGroup(self::API_NAME, self::ENDPOINT_NAME, $groupSlug));
70+
}
71+
}
72+
73+
/**
74+
* @param Schema $schema
75+
*/
76+
public function down(Schema $schema): void
77+
{
78+
$scope = MemberScopes::ReadMemberData;
79+
80+
// Reverse order: authz groups → endpoint scopes → endpoint
81+
$authzGroups = [
82+
IGroup::SuperAdmins,
83+
IGroup::Administrators,
84+
IGroup::SummitAdministrators,
85+
];
86+
87+
foreach ($authzGroups as $groupSlug) {
88+
$this->addSql($this->deleteEndpointAuthzGroup(self::API_NAME, self::ENDPOINT_NAME, $groupSlug));
89+
}
90+
91+
$this->addSql($this->deleteScopesEndpoints(self::API_NAME, [$scope]));
92+
$this->addSql($this->deleteEndpoint(self::API_NAME, self::ENDPOINT_NAME));
93+
}
94+
}

database/seeders/ApiEndpointsSeeder.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9410,6 +9410,17 @@ private function seedMemberEndpoints()
94109410
'http_method' => 'GET',
94119411
'scopes' => [MemberScopes::ReadMemberData],
94129412
],
9413+
[
9414+
'name' => 'get-member-by-external-id',
9415+
'route' => '/api/v1/members/external/{external_id}',
9416+
'http_method' => 'GET',
9417+
'scopes' => [MemberScopes::ReadMemberData],
9418+
'authz_groups' => [
9419+
IGroup::SuperAdmins,
9420+
IGroup::Administrators,
9421+
IGroup::SummitAdministrators,
9422+
]
9423+
],
94139424
[
94149425
'name' => 'get-my-member',
94159426
'route' => '/api/v1/members/me',

routes/api_v1.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
// members
2525
Route::group(['prefix' => 'members'], function () {
2626
Route::get('', 'OAuth2MembersApiController@getAll');
27-
27+
Route::group(['prefix' => 'external'], function () {
28+
Route::group(['prefix' => '{external_id}'], function () {
29+
Route::get('', ['middleware' => 'auth.user', 'uses' => 'OAuth2MembersApiController@getMemberByIdExternalId']);
30+
});
31+
});
2832
Route::group(['prefix' => 'me'], function () {
2933
// get my member info
3034
Route::get('', 'OAuth2MembersApiController@getMyMember');

0 commit comments

Comments
 (0)