API: Add /users/{id}/angeltypes, updated openapi formatting

This commit is contained in:
Igor Scheller 2023-11-03 23:27:31 +01:00 committed by Michael Weimann
parent 497c1772f7
commit 5b237febf8
4 changed files with 125 additions and 9 deletions

View File

@ -123,6 +123,7 @@ $route->addGroup(
$route->get('/news', 'Api\NewsController@index'); $route->get('/news', 'Api\NewsController@index');
$route->get('/locations', 'Api\LocationsController@index'); $route->get('/locations', 'Api\LocationsController@index');
$route->get('/locations/{location_id:\d+}/shifts', 'Api\ShiftsController@entriesByLocation'); $route->get('/locations/{location_id:\d+}/shifts', 'Api\ShiftsController@entriesByLocation');
$route->get('/users/{user_id:\d+}/angeltypes', 'Api\AngelTypeController@ofUser');
$route->addRoute( $route->addRoute(
['POST', 'PUT', 'DELETE', 'PATCH'], ['POST', 'PUT', 'DELETE', 'PATCH'],

View File

@ -3,7 +3,7 @@ openapi: 3.0.3
info: info:
version: 0.0.1-beta version: 0.0.1-beta
title: Engelsystem title: Engelsystem
description: | description: >
This API is as stable as a **beta** version might be. This API is as stable as a **beta** version might be.
It could burst into flames and morph into a monster at any second! 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). (But we try to keep it somewhat consistent, at least during events).
@ -25,6 +25,8 @@ tags:
description: News and meeting announcements description: News and meeting announcements
- name: shift - name: shift
description: Event shifts and location description: Event shifts and location
- name: user
description: User information
components: components:
securitySchemes: securitySchemes:
@ -81,6 +83,24 @@ components:
- name - name
- description - description
- url - 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: Error:
type: object type: object
properties: properties:
@ -168,9 +188,9 @@ components:
example: Cleanup the venue example: Cleanup the venue
description: description:
type: string type: string
example: You clean up the venue after the event, its fun, we promise! example: You clean up the venue after the event. Its fun, we promise!
description: | description: >
Shift description, should be added to the shift type description as it might be empty. Shift description, should be added to the shift type description but might be empty.
Normally contains additional information for a specific shift / task. Normally contains additional information for a specific shift / task.
starts_at: starts_at:
type: string type: string
@ -206,7 +226,7 @@ components:
url: url:
type: string type: string
example: https://example.com/shifts/42 example: https://example.com/shifts/42
description: Direct link of the shift description: Direct link to the shift
required: required:
- id - id
- title - title
@ -230,9 +250,10 @@ components:
$ref: '#/components/schemas/AngelType' $ref: '#/components/schemas/AngelType'
needs: needs:
type: integer type: integer
description: | description: >
Number of users needed for the shift. Number of users needed for the shift of the given type.
Can be more than users count when not full, less when overbooked or 0 when additional users have been added. 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 example: 3
required: required:
- users - users
@ -402,3 +423,35 @@ paths:
$ref: '#/components/responses/ForbiddenError' $ref: '#/components/responses/ForbiddenError'
'404': '404':
$ref: '#/components/responses/NotFoundError' $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'

View File

@ -4,8 +4,10 @@ declare(strict_types=1);
namespace Engelsystem\Controllers\Api; namespace Engelsystem\Controllers\Api;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\AngelType; use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User;
class AngelTypeController extends ApiController class AngelTypeController extends ApiController
{ {
@ -16,11 +18,43 @@ class AngelTypeController extends ApiController
->get(['id', 'name', 'description']); ->get(['id', 'name', 'description']);
$models->map(function (AngelType $model): void { $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]; $data = ['data' => $models];
return $this->response return $this->response
->withContent(json_encode($data)); ->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]);
}
} }

View File

@ -5,13 +5,17 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api; namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Controllers\Api\AngelTypeController; use Engelsystem\Controllers\Api\AngelTypeController;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Models\AngelType; use Engelsystem\Models\AngelType;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
class AngelTypeControllerTest extends ApiBaseControllerTest class AngelTypeControllerTest extends ApiBaseControllerTest
{ {
/** /**
* @covers \Engelsystem\Controllers\Api\AngelTypeController::index * @covers \Engelsystem\Controllers\Api\AngelTypeController::index
* @covers \Engelsystem\Controllers\Api\AngelTypeController::getUrl
*/ */
public function testIndex(): void public function testIndex(): void
{ {
@ -33,4 +37,28 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
return $item['name'] == $items->first()->getAttribute('name'); 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;
}));
}
} }