From cd70cb73af7c61cde40f9103092603fe82aad378 Mon Sep 17 00:00:00 2001 From: smarcet Date: Tue, 13 Oct 2026 20:49:56 -0300 Subject: [PATCH] fix(events): allow ordering location events by location The Room Occupancy page sends the same order param to the summit-wide /events/published and to /locations/{id}/events/published. The summit-wide endpoint already accepts order=location; the location endpoints rejected it with a validation error. Add location to the location events order allowlist (reusing the existing COALESCE(LOWER(l.name), '') mapping and the l join) and update the OpenAPI annotations. Tests cover +/-location on both location endpoints (real room and tbd) and the ordering on the summit-wide published endpoint. --- .../OAuth2SummitLocationsApiController.php | 7 ++-- tests/oauth2/OAuth2SummitLocationsApiTest.php | 41 ++++++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php index e647bad7e..f996e6924 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitLocationsApiController.php @@ -1477,6 +1477,7 @@ function () { 'track_id', 'occupancy', 'track', + 'location', ]; }, function ($filter) use($summit_id, $location, $published){ @@ -1539,7 +1540,7 @@ function ($page, $per_page, $filter, $order, $applyExtraFilters) use ($location) new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'location_id', in: 'path', required: true, description: 'Location ID or "tbd"', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter by: title, start_date, end_date, speaker, tags, event_type_id, track_id, type_show_always_on_schedule', schema: new OA\Schema(type: 'string')), - new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track, location', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), new OA\Parameter(name: 'expand', in: 'query', required: false, description: 'Expand related entities', schema: new OA\Schema(type: 'string')), @@ -1570,7 +1571,7 @@ public function getLocationEvents($summit_id, $location_id) new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'location_id', in: 'path', required: true, description: 'Location ID or "tbd"', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter by: title, start_date, end_date, speaker, tags, event_type_id, track_id, type_show_always_on_schedule', schema: new OA\Schema(type: 'string')), - new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track, location', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), new OA\Parameter(name: 'expand', in: 'query', required: false, description: 'Expand related entities', schema: new OA\Schema(type: 'string')), @@ -1599,7 +1600,7 @@ public function getLocationEvents($summit_id, $location_id) new OA\Parameter(name: 'id', in: 'path', required: true, description: 'Summit ID or slug', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'location_id', in: 'path', required: true, description: 'Location ID or "tbd"', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'filter', in: 'query', required: false, description: 'Filter by: title, start_date, end_date, speaker, tags, event_type_id, track_id, type_show_always_on_schedule', schema: new OA\Schema(type: 'string')), - new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track', schema: new OA\Schema(type: 'string')), + new OA\Parameter(name: 'order', in: 'query', required: false, description: 'Order by: title, start_date, end_date, id, created, track_id, occupancy, track, location', schema: new OA\Schema(type: 'string')), new OA\Parameter(name: 'page', in: 'query', required: false, description: 'Page number', schema: new OA\Schema(type: 'integer', default: 1)), new OA\Parameter(name: 'per_page', in: 'query', required: false, description: 'Items per page', schema: new OA\Schema(type: 'integer', default: 10)), new OA\Parameter(name: 'expand', in: 'query', required: false, description: 'Expand related entities', schema: new OA\Schema(type: 'string')), diff --git a/tests/oauth2/OAuth2SummitLocationsApiTest.php b/tests/oauth2/OAuth2SummitLocationsApiTest.php index 5e062d24b..4b3f84fe8 100644 --- a/tests/oauth2/OAuth2SummitLocationsApiTest.php +++ b/tests/oauth2/OAuth2SummitLocationsApiTest.php @@ -372,7 +372,7 @@ public function testCurrentSummitPublishedLocationEventsWithFilter() public static function locationEventsOrderProvider(): array { $cases = []; - foreach (['+occupancy', '-occupancy', '+track'] as $order) { + foreach (['+occupancy', '-occupancy', '+track', '+location', '-location'] as $order) { $cases["real location $order"] = [false, $order]; $cases["tbd location $order"] = [true, $order]; } @@ -520,6 +520,45 @@ public function testGetLocationPublishedEventsOrderByTrack() } } + private function getPublishedEventsHasLocation(string $order): array + { + $response = $this->action( + "GET", + "OAuth2SummitEventsApiController@getScheduledEvents", + [ + 'id' => self::$summit->getId(), + 'page' => 1, + 'per_page' => 100, + 'order' => $order, + ], + [], + [], + [], + $this->getAuthHeaders() + ); + + $this->assertResponseStatus(200); + $page = json_decode($response->getContent()); + $this->assertNotEmpty($page->data); + + return array_map(fn($event) => intval($event->location_id) > 0 ? 1 : 0, $page->data); + } + + public function testGetPublishedEventsOrderByLocation() + { + // events without location sort as '' (COALESCE), so they come before the main venue on ascending order + $asc = $this->getPublishedEventsHasLocation('+location'); + $sorted_asc = $asc; + sort($sorted_asc); + $this->assertSame($sorted_asc, $asc); + $this->assertCount(2, array_unique($asc), 'fixtures should mix events with and without location'); + + $desc = $this->getPublishedEventsHasLocation('-location'); + $sorted_desc = $desc; + rsort($sorted_desc); + $this->assertSame($sorted_desc, $desc); + } + public function testAddLocationWithoutClassName(){ $params = [