API: Add urls to response, configure required fields

This commit is contained in:
Igor Scheller 2023-07-27 14:34:21 +02:00 committed by Michael Weimann
parent a5cebc8535
commit ea93e27a9d
13 changed files with 121 additions and 20 deletions

View File

@ -63,6 +63,17 @@ components:
name:
type: string
example: Angel
description:
type: string
example: Meta-Group of all registered Angels
url:
type: string
example: https://example.com/news/42
required:
- id
- name
- description
- url
Error:
type: object
properties:
@ -105,6 +116,19 @@ components:
format: date-time
description: DateTime in ISO-8601 format
example: 2023-05-13T23:00:00.000000Z
url:
type: string
example: https://example.com/news/42
required:
- id
- title
- text
- is_meeting
- is_pinned
- is_highlighted
- created_at
- updated_at
- url
Room:
type: object
properties:
@ -114,6 +138,13 @@ components:
name:
type: string
example: Heaven
url:
type: string
example: https://example.com/rooms/42
required:
- id
- name
- url
Shift:
type: object
properties:
@ -156,6 +187,21 @@ components:
format: date-time
description: DateTime in ISO-8601 format
example: 2023-05-13T23:00:00.000000Z
url:
type: string
example: https://example.com/shifts/42
required:
- id
- title
- description
- start
- end
- entries
- room
- shift_type
- created_at
- updated_at
- url
ShiftEntry:
type: object
properties:
@ -163,6 +209,9 @@ components:
$ref: '#/components/schemas/User'
type:
$ref: '#/components/schemas/AngelType'
required:
- user
- type
ShiftType:
type: object
properties:
@ -175,6 +224,10 @@ components:
description:
type: string
example: This is a generic build-up shift, mostly involving heavy lifting.
required:
- id
- name
- description
User:
type: object
properties:
@ -207,6 +260,17 @@ components:
type: string
nullable: true
example: 1234567890
url:
type: string
example: https://example.com/users/42
required:
- id
- name
- first_name
- last_name
- pronoun
- contact
- url
security:
- bearerAuth: [ ]

View File

@ -11,11 +11,15 @@ class AngelTypeController extends ApiController
{
public function index(): Response
{
$news = AngelType::query()
$models = AngelType::query()
->orderBy('name')
->get(['id', 'name']);
->get(['id', 'name', 'description']);
$data = ['data' => $news];
$models->map(function (AngelType $model): void {
$model->url = $this->url->to('/angeltypes', ['action' => 'view', 'angeltype_id' => $model->id]);
});
$data = ['data' => $models];
return $this->response
->withContent(json_encode($data));
}

View File

@ -6,6 +6,7 @@ namespace Engelsystem\Controllers\Api;
use Engelsystem\Controllers\BaseController;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGeneratorInterface;
abstract class ApiController extends BaseController
{
@ -13,7 +14,7 @@ abstract class ApiController extends BaseController
'api',
];
public function __construct(protected Response $response)
public function __construct(protected Response $response, protected UrlGeneratorInterface $url)
{
$this->response = $this->response
->withHeader('content-type', 'application/json')

View File

@ -11,12 +11,16 @@ class NewsController extends ApiController
{
public function index(): Response
{
$news = News::query()
$models = News::query()
->orderByDesc('updated_at')
->orderByDesc('created_at')
->get(['id', 'title', 'text', 'is_meeting', 'is_pinned', 'is_highlighted', 'created_at', 'updated_at']);
$data = ['data' => $news];
$models->map(function (News $model): void {
$model->url = $this->url->to('/news/' . $model->id);
});
$data = ['data' => $models];
return $this->response
->withContent(json_encode($data));
}

View File

@ -11,11 +11,15 @@ class RoomsController extends ApiController
{
public function index(): Response
{
$news = Room::query()
$models = Room::query()
->orderBy('name')
->get(['id', 'name']);
$data = ['data' => $news];
$models->map(function (Room $model): void {
$model->url = $this->url->to('/rooms', ['action' => 'view', 'room_id' => $model->id]);
});
$data = ['data' => $models];
return $this->response
->withContent(json_encode($data));
}

View File

@ -37,14 +37,24 @@ class ShiftsController extends ApiController
'last_name' => $user->personalData->last_name,
'pronoun' => $user->personalData->pronoun,
'contact' => $user->contact->only(['dect', 'mobile']),
'url' => $this->url->to('/users', ['action' => 'view', 'user_id' => $user->id]),
];
$angelTypeData = $entry->angelType->only(['id', 'name']);
$angelTypeData['url'] = $this->url->to(
'/angeltypes',
['action' => 'view', 'angeltype_id' => $entry->angelType->id]
);
$entries[] = [
'user' => $userData,
'type' => $entry->angelType->only(['id', 'name']),
'type' => $angelTypeData,
];
}
$roomData = $room->only(['id', 'name']);
$roomData['url'] = $this->url->to('/rooms', ['action' => 'view', 'room_id' => $room->id]);
$shiftEntries[] = [
'id' => $shift->id,
'title' => $shift->title,
@ -52,10 +62,11 @@ class ShiftsController extends ApiController
'start' => $shift->start,
'end' => $shift->end,
'entries' => $entries,
'room' => $room->only(['id', 'name']),
'room' => $roomData,
'shift_type' => $shift->shiftType->only(['id', 'name', 'description']),
'created_at' => $shift->created_at,
'updated_at' => $shift->updated_at,
'url' => $this->url->to('/shifts', ['action' => 'view', 'shift_id' => $shift->id]),
];
}

View File

@ -18,7 +18,7 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
$this->initDatabase();
AngelType::factory(3)->create();
$controller = new AngelTypeController(new Response());
$controller = new AngelTypeController(new Response(), $this->url);
$response = $controller->index();
$this->validateApiResponse('/angeltypes', 'get', $response);

View File

@ -4,15 +4,18 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Controllers\Api;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Test\Unit\Controllers\ControllerTest as TestCase;
use League\OpenAPIValidation\PSR7\OperationAddress as OpenApiAddress;
use League\OpenAPIValidation\PSR7\ResponseValidator as OpenApiResponseValidator;
use League\OpenAPIValidation\PSR7\ValidatorBuilder as OpenApiValidatorBuilder;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Message\ResponseInterface;
abstract class ApiBaseControllerTest extends TestCase
{
protected OpenApiResponseValidator $validator;
protected UrlGeneratorInterface $url;
protected function validateApiResponse(string $path, string $method, ResponseInterface $response): void
{
@ -29,5 +32,15 @@ abstract class ApiBaseControllerTest extends TestCase
$this->validator = (new OpenApiValidatorBuilder())
->fromYamlFile($openApiDefinition)
->getResponseValidator();
/** @var UrlGeneratorInterface|MockObject $url */
$url = $this->getMockForAbstractClass(UrlGeneratorInterface::class);
$url->expects($this->any())
->method('to')
->willReturnCallback(function (string $path, array $params): string {
$query = http_build_query($params);
return $path . ($query ? '?' . $query : '');
});
$this->url = $url;
}
}

View File

@ -14,7 +14,7 @@ class ApiControllerTest extends ApiBaseControllerTest
*/
public function testConstruct(): void
{
$controller = new class (new Response('{"some":"json"}')) extends ApiController {
$controller = new class (new Response('{"some":"json"}'), $this->url) extends ApiController {
public function getResponse(): Response
{
return $this->response;

View File

@ -15,7 +15,7 @@ class IndexControllerTest extends ApiBaseControllerTest
*/
public function testIndex(): void
{
$controller = new IndexController(new Response());
$controller = new IndexController(new Response(), $this->url);
$response = $controller->index();
$this->assertEquals(200, $response->getStatusCode());
@ -32,7 +32,7 @@ class IndexControllerTest extends ApiBaseControllerTest
*/
public function testIndexV0(): void
{
$controller = new IndexController(new Response());
$controller = new IndexController(new Response(), $this->url);
$response = $controller->indexV0();
$this->assertEquals(200, $response->getStatusCode());
@ -48,7 +48,7 @@ class IndexControllerTest extends ApiBaseControllerTest
*/
public function testOptions(): void
{
$controller = new IndexController(new Response());
$controller = new IndexController(new Response(), $this->url);
$response = $controller->options();
$this->assertEquals(204, $response->getStatusCode());
@ -61,7 +61,7 @@ class IndexControllerTest extends ApiBaseControllerTest
*/
public function testNotImplemented(): void
{
$controller = new IndexController(new Response());
$controller = new IndexController(new Response(), $this->url);
$response = $controller->notImplemented();
$this->assertEquals(501, $response->getStatusCode());

View File

@ -17,7 +17,7 @@ class NewsControllerTest extends ApiBaseControllerTest
{
News::factory(3)->create();
$controller = new NewsController(new Response());
$controller = new NewsController(new Response(), $this->url);
$response = $controller->index();
$this->validateApiResponse('/news', 'get', $response);

View File

@ -18,7 +18,7 @@ class RoomsControllerTest extends ApiBaseControllerTest
$this->initDatabase();
Room::factory(3)->create();
$controller = new RoomsController(new Response());
$controller = new RoomsController(new Response(), $this->url);
$response = $controller->index();
$this->validateApiResponse('/rooms', 'get', $response);

View File

@ -40,10 +40,10 @@ class ShiftsControllerTest extends ApiBaseControllerTest
$request = new Request();
$request = $request->withAttribute('room_id', $room->id);
$controller = new ShiftsController(new Response());
$controller = new ShiftsController(new Response(), $this->url);
$response = $controller->entriesByRoom($request);
$this->validateApiResponse('/rooms', 'get', $response);
$this->validateApiResponse('/rooms/{id}/shifts', 'get', $response);
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
$this->assertJson($response->getContent());