From 5b237febf8228394f35f5e8f018735f6e7b8a8a7 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Fri, 3 Nov 2023 23:27:31 +0100 Subject: [PATCH] API: Add /users/{id}/angeltypes, updated openapi formatting --- config/routes.php | 1 + resources/api/openapi.yml | 69 ++++++++++++++++--- src/Controllers/Api/AngelTypeController.php | 36 +++++++++- .../Api/AngelTypeControllerTest.php | 28 ++++++++ 4 files changed, 125 insertions(+), 9 deletions(-) diff --git a/config/routes.php b/config/routes.php index 451fd128..1f27a7e6 100644 --- a/config/routes.php +++ b/config/routes.php @@ -123,6 +123,7 @@ $route->addGroup( $route->get('/news', 'Api\NewsController@index'); $route->get('/locations', 'Api\LocationsController@index'); $route->get('/locations/{location_id:\d+}/shifts', 'Api\ShiftsController@entriesByLocation'); + $route->get('/users/{user_id:\d+}/angeltypes', 'Api\AngelTypeController@ofUser'); $route->addRoute( ['POST', 'PUT', 'DELETE', 'PATCH'], diff --git a/resources/api/openapi.yml b/resources/api/openapi.yml index 9b9023ee..c6cdca8d 100644 --- a/resources/api/openapi.yml +++ b/resources/api/openapi.yml @@ -3,7 +3,7 @@ openapi: 3.0.3 info: version: 0.0.1-beta title: Engelsystem - description: | + description: > This API is as stable as a **beta** version might be. It could burst into flames and morph into a monster at any second! (But we try to keep it somewhat consistent, at least during events). @@ -25,6 +25,8 @@ tags: description: News and meeting announcements - name: shift description: Event shifts and location + - name: user + description: User information components: securitySchemes: @@ -81,6 +83,24 @@ components: - name - description - url + UserAngelType: + allOf: + - $ref: '#/components/schemas/AngelType' + - type: object + properties: + confirmed: + type: boolean + example: true + description: > + If the user is confirmed + (either by the angeltype not requiring confirmation, being a supporter or being confirmed by one). + supporter: + type: boolean + example: false + description: If the user is a supporter of the angeltype. + required: + - confirmed + - supporter Error: type: object properties: @@ -168,9 +188,9 @@ components: example: Cleanup the venue description: type: string - example: You clean up the venue after the event, its fun, we promise! - description: | - Shift description, should be added to the shift type description as it might be empty. + example: You clean up the venue after the event. Its fun, we promise! + description: > + Shift description, should be added to the shift type description but might be empty. Normally contains additional information for a specific shift / task. starts_at: type: string @@ -206,7 +226,7 @@ components: url: type: string example: https://example.com/shifts/42 - description: Direct link of the shift + description: Direct link to the shift required: - id - title @@ -230,9 +250,10 @@ components: $ref: '#/components/schemas/AngelType' needs: type: integer - description: | - Number of users needed for the shift. - Can be more than users count when not full, less when overbooked or 0 when additional users have been added. + description: > + Number of users needed for the shift of the given type. + Will be more than users count when not full, less when overbooked + or 0 when only additional users with given type have been added. example: 3 required: - users @@ -402,3 +423,35 @@ paths: $ref: '#/components/responses/ForbiddenError' '404': $ref: '#/components/responses/NotFoundError' + + /users/{id}/angeltypes: + parameters: + - name: id + in: path + required: true + description: The user identifier + example: 42 + schema: + type: integer + get: + tags: + - user + summary: Get the users angel types + responses: + '200': + description: Ok + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/UserAngelType' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/NotFoundError' diff --git a/src/Controllers/Api/AngelTypeController.php b/src/Controllers/Api/AngelTypeController.php index c2a022ff..46314361 100644 --- a/src/Controllers/Api/AngelTypeController.php +++ b/src/Controllers/Api/AngelTypeController.php @@ -4,8 +4,10 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Api; +use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Models\AngelType; +use Engelsystem\Models\User\User; class AngelTypeController extends ApiController { @@ -16,11 +18,43 @@ class AngelTypeController extends ApiController ->get(['id', 'name', 'description']); $models->map(function (AngelType $model): void { - $model->url = $this->url->to('/angeltypes', ['action' => 'view', 'angeltype_id' => $model->id]); + $model->url = $this->getUrl($model); }); $data = ['data' => $models]; return $this->response ->withContent(json_encode($data)); } + + public function ofUser(Request $request): Response + { + $id = (int) $request->getAttribute('user_id'); + $model = User::findOrFail($id); + + $models = $model->userAngelTypes()->get([ + 'angel_types.id', + 'angel_types.name', + 'angel_types.description', + 'angel_types.restricted', + ]); + $data = []; + + $models->map(function (AngelType $model) use (&$data): void { + $model->confirmed = !$model->restricted || $model->pivot->supporter || $model->pivot->confirm_user_id; + $model->supporter = $model->pivot->supporter; + $model->url = $this->getUrl($model); + $modelData = $model->attributesToArray(); + unset($modelData['restricted']); + $data[] = $modelData; + }); + + $data = ['data' => $data]; + return $this->response + ->withContent(json_encode($data)); + } + + protected function getUrl(AngelType $model): string + { + return $this->url->to('/angeltypes', ['action' => 'view', 'angeltype_id' => $model->id]); + } } diff --git a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php index e3dc57a6..8b69bb27 100644 --- a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php +++ b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php @@ -5,13 +5,17 @@ declare(strict_types=1); namespace Engelsystem\Test\Unit\Controllers\Api; use Engelsystem\Controllers\Api\AngelTypeController; +use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Models\AngelType; +use Engelsystem\Models\User\User; +use Engelsystem\Models\UserAngelType; class AngelTypeControllerTest extends ApiBaseControllerTest { /** * @covers \Engelsystem\Controllers\Api\AngelTypeController::index + * @covers \Engelsystem\Controllers\Api\AngelTypeController::getUrl */ public function testIndex(): void { @@ -33,4 +37,28 @@ class AngelTypeControllerTest extends ApiBaseControllerTest return $item['name'] == $items->first()->getAttribute('name'); })); } + /** + * @covers \Engelsystem\Controllers\Api\AngelTypeController::ofUser + */ + public function testOfUser(): void + { + $this->initDatabase(); + $user = User::factory()->create(); + $items = UserAngelType::factory(3)->create(['user_id' => $user->id]); + + $controller = new AngelTypeController(new Response(), $this->url); + + $response = $controller->ofUser(new Request([], [], ['user_id' => $user->id])); + $this->validateApiResponse('/users/{id}/angeltypes', 'get', $response); + + $this->assertEquals(['application/json'], $response->getHeader('content-type')); + $this->assertJson($response->getContent()); + + $data = json_decode($response->getContent(), true); + $this->assertArrayHasKey('data', $data); + $this->assertCount(3, $data['data']); + $this->assertCount(1, collect($data['data'])->filter(function ($item) use ($items) { + return $item['name'] == $items->first()->angelType->name; + })); + } }