API: Add urls to response, configure required fields
This commit is contained in:
parent
a5cebc8535
commit
ea93e27a9d
|
@ -63,6 +63,17 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: Angel
|
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:
|
Error:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -105,6 +116,19 @@ components:
|
||||||
format: date-time
|
format: date-time
|
||||||
description: DateTime in ISO-8601 format
|
description: DateTime in ISO-8601 format
|
||||||
example: 2023-05-13T23:00:00.000000Z
|
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:
|
Room:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -114,6 +138,13 @@ components:
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
example: Heaven
|
example: Heaven
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: https://example.com/rooms/42
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- url
|
||||||
Shift:
|
Shift:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -156,6 +187,21 @@ components:
|
||||||
format: date-time
|
format: date-time
|
||||||
description: DateTime in ISO-8601 format
|
description: DateTime in ISO-8601 format
|
||||||
example: 2023-05-13T23:00:00.000000Z
|
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:
|
ShiftEntry:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -163,6 +209,9 @@ components:
|
||||||
$ref: '#/components/schemas/User'
|
$ref: '#/components/schemas/User'
|
||||||
type:
|
type:
|
||||||
$ref: '#/components/schemas/AngelType'
|
$ref: '#/components/schemas/AngelType'
|
||||||
|
required:
|
||||||
|
- user
|
||||||
|
- type
|
||||||
ShiftType:
|
ShiftType:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -175,6 +224,10 @@ components:
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
example: This is a generic build-up shift, mostly involving heavy lifting.
|
example: This is a generic build-up shift, mostly involving heavy lifting.
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- description
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@ -207,6 +260,17 @@ components:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
nullable: true
|
||||||
example: 1234567890
|
example: 1234567890
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
example: https://example.com/users/42
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- first_name
|
||||||
|
- last_name
|
||||||
|
- pronoun
|
||||||
|
- contact
|
||||||
|
- url
|
||||||
|
|
||||||
security:
|
security:
|
||||||
- bearerAuth: [ ]
|
- bearerAuth: [ ]
|
||||||
|
|
|
@ -11,11 +11,15 @@ class AngelTypeController extends ApiController
|
||||||
{
|
{
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$news = AngelType::query()
|
$models = AngelType::query()
|
||||||
->orderBy('name')
|
->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
|
return $this->response
|
||||||
->withContent(json_encode($data));
|
->withContent(json_encode($data));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Engelsystem\Controllers\Api;
|
||||||
|
|
||||||
use Engelsystem\Controllers\BaseController;
|
use Engelsystem\Controllers\BaseController;
|
||||||
use Engelsystem\Http\Response;
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Http\UrlGeneratorInterface;
|
||||||
|
|
||||||
abstract class ApiController extends BaseController
|
abstract class ApiController extends BaseController
|
||||||
{
|
{
|
||||||
|
@ -13,7 +14,7 @@ abstract class ApiController extends BaseController
|
||||||
'api',
|
'api',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(protected Response $response)
|
public function __construct(protected Response $response, protected UrlGeneratorInterface $url)
|
||||||
{
|
{
|
||||||
$this->response = $this->response
|
$this->response = $this->response
|
||||||
->withHeader('content-type', 'application/json')
|
->withHeader('content-type', 'application/json')
|
||||||
|
|
|
@ -11,12 +11,16 @@ class NewsController extends ApiController
|
||||||
{
|
{
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$news = News::query()
|
$models = News::query()
|
||||||
->orderByDesc('updated_at')
|
->orderByDesc('updated_at')
|
||||||
->orderByDesc('created_at')
|
->orderByDesc('created_at')
|
||||||
->get(['id', 'title', 'text', 'is_meeting', 'is_pinned', 'is_highlighted', 'created_at', 'updated_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
|
return $this->response
|
||||||
->withContent(json_encode($data));
|
->withContent(json_encode($data));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,11 +11,15 @@ class RoomsController extends ApiController
|
||||||
{
|
{
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$news = Room::query()
|
$models = Room::query()
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get(['id', '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
|
return $this->response
|
||||||
->withContent(json_encode($data));
|
->withContent(json_encode($data));
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,14 +37,24 @@ class ShiftsController extends ApiController
|
||||||
'last_name' => $user->personalData->last_name,
|
'last_name' => $user->personalData->last_name,
|
||||||
'pronoun' => $user->personalData->pronoun,
|
'pronoun' => $user->personalData->pronoun,
|
||||||
'contact' => $user->contact->only(['dect', 'mobile']),
|
'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[] = [
|
$entries[] = [
|
||||||
'user' => $userData,
|
'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[] = [
|
$shiftEntries[] = [
|
||||||
'id' => $shift->id,
|
'id' => $shift->id,
|
||||||
'title' => $shift->title,
|
'title' => $shift->title,
|
||||||
|
@ -52,10 +62,11 @@ class ShiftsController extends ApiController
|
||||||
'start' => $shift->start,
|
'start' => $shift->start,
|
||||||
'end' => $shift->end,
|
'end' => $shift->end,
|
||||||
'entries' => $entries,
|
'entries' => $entries,
|
||||||
'room' => $room->only(['id', 'name']),
|
'room' => $roomData,
|
||||||
'shift_type' => $shift->shiftType->only(['id', 'name', 'description']),
|
'shift_type' => $shift->shiftType->only(['id', 'name', 'description']),
|
||||||
'created_at' => $shift->created_at,
|
'created_at' => $shift->created_at,
|
||||||
'updated_at' => $shift->updated_at,
|
'updated_at' => $shift->updated_at,
|
||||||
|
'url' => $this->url->to('/shifts', ['action' => 'view', 'shift_id' => $shift->id]),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ class AngelTypeControllerTest extends ApiBaseControllerTest
|
||||||
$this->initDatabase();
|
$this->initDatabase();
|
||||||
AngelType::factory(3)->create();
|
AngelType::factory(3)->create();
|
||||||
|
|
||||||
$controller = new AngelTypeController(new Response());
|
$controller = new AngelTypeController(new Response(), $this->url);
|
||||||
|
|
||||||
$response = $controller->index();
|
$response = $controller->index();
|
||||||
$this->validateApiResponse('/angeltypes', 'get', $response);
|
$this->validateApiResponse('/angeltypes', 'get', $response);
|
||||||
|
|
|
@ -4,15 +4,18 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Engelsystem\Test\Unit\Controllers\Api;
|
namespace Engelsystem\Test\Unit\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Http\UrlGeneratorInterface;
|
||||||
use Engelsystem\Test\Unit\Controllers\ControllerTest as TestCase;
|
use Engelsystem\Test\Unit\Controllers\ControllerTest as TestCase;
|
||||||
use League\OpenAPIValidation\PSR7\OperationAddress as OpenApiAddress;
|
use League\OpenAPIValidation\PSR7\OperationAddress as OpenApiAddress;
|
||||||
use League\OpenAPIValidation\PSR7\ResponseValidator as OpenApiResponseValidator;
|
use League\OpenAPIValidation\PSR7\ResponseValidator as OpenApiResponseValidator;
|
||||||
use League\OpenAPIValidation\PSR7\ValidatorBuilder as OpenApiValidatorBuilder;
|
use League\OpenAPIValidation\PSR7\ValidatorBuilder as OpenApiValidatorBuilder;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
abstract class ApiBaseControllerTest extends TestCase
|
abstract class ApiBaseControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
protected OpenApiResponseValidator $validator;
|
protected OpenApiResponseValidator $validator;
|
||||||
|
protected UrlGeneratorInterface $url;
|
||||||
|
|
||||||
protected function validateApiResponse(string $path, string $method, ResponseInterface $response): void
|
protected function validateApiResponse(string $path, string $method, ResponseInterface $response): void
|
||||||
{
|
{
|
||||||
|
@ -29,5 +32,15 @@ abstract class ApiBaseControllerTest extends TestCase
|
||||||
$this->validator = (new OpenApiValidatorBuilder())
|
$this->validator = (new OpenApiValidatorBuilder())
|
||||||
->fromYamlFile($openApiDefinition)
|
->fromYamlFile($openApiDefinition)
|
||||||
->getResponseValidator();
|
->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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ApiControllerTest extends ApiBaseControllerTest
|
||||||
*/
|
*/
|
||||||
public function testConstruct(): void
|
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
|
public function getResponse(): Response
|
||||||
{
|
{
|
||||||
return $this->response;
|
return $this->response;
|
||||||
|
|
|
@ -15,7 +15,7 @@ class IndexControllerTest extends ApiBaseControllerTest
|
||||||
*/
|
*/
|
||||||
public function testIndex(): void
|
public function testIndex(): void
|
||||||
{
|
{
|
||||||
$controller = new IndexController(new Response());
|
$controller = new IndexController(new Response(), $this->url);
|
||||||
$response = $controller->index();
|
$response = $controller->index();
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
@ -32,7 +32,7 @@ class IndexControllerTest extends ApiBaseControllerTest
|
||||||
*/
|
*/
|
||||||
public function testIndexV0(): void
|
public function testIndexV0(): void
|
||||||
{
|
{
|
||||||
$controller = new IndexController(new Response());
|
$controller = new IndexController(new Response(), $this->url);
|
||||||
$response = $controller->indexV0();
|
$response = $controller->indexV0();
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
@ -48,7 +48,7 @@ class IndexControllerTest extends ApiBaseControllerTest
|
||||||
*/
|
*/
|
||||||
public function testOptions(): void
|
public function testOptions(): void
|
||||||
{
|
{
|
||||||
$controller = new IndexController(new Response());
|
$controller = new IndexController(new Response(), $this->url);
|
||||||
$response = $controller->options();
|
$response = $controller->options();
|
||||||
|
|
||||||
$this->assertEquals(204, $response->getStatusCode());
|
$this->assertEquals(204, $response->getStatusCode());
|
||||||
|
@ -61,7 +61,7 @@ class IndexControllerTest extends ApiBaseControllerTest
|
||||||
*/
|
*/
|
||||||
public function testNotImplemented(): void
|
public function testNotImplemented(): void
|
||||||
{
|
{
|
||||||
$controller = new IndexController(new Response());
|
$controller = new IndexController(new Response(), $this->url);
|
||||||
$response = $controller->notImplemented();
|
$response = $controller->notImplemented();
|
||||||
|
|
||||||
$this->assertEquals(501, $response->getStatusCode());
|
$this->assertEquals(501, $response->getStatusCode());
|
||||||
|
|
|
@ -17,7 +17,7 @@ class NewsControllerTest extends ApiBaseControllerTest
|
||||||
{
|
{
|
||||||
News::factory(3)->create();
|
News::factory(3)->create();
|
||||||
|
|
||||||
$controller = new NewsController(new Response());
|
$controller = new NewsController(new Response(), $this->url);
|
||||||
|
|
||||||
$response = $controller->index();
|
$response = $controller->index();
|
||||||
$this->validateApiResponse('/news', 'get', $response);
|
$this->validateApiResponse('/news', 'get', $response);
|
||||||
|
|
|
@ -18,7 +18,7 @@ class RoomsControllerTest extends ApiBaseControllerTest
|
||||||
$this->initDatabase();
|
$this->initDatabase();
|
||||||
Room::factory(3)->create();
|
Room::factory(3)->create();
|
||||||
|
|
||||||
$controller = new RoomsController(new Response());
|
$controller = new RoomsController(new Response(), $this->url);
|
||||||
|
|
||||||
$response = $controller->index();
|
$response = $controller->index();
|
||||||
$this->validateApiResponse('/rooms', 'get', $response);
|
$this->validateApiResponse('/rooms', 'get', $response);
|
||||||
|
|
|
@ -40,10 +40,10 @@ class ShiftsControllerTest extends ApiBaseControllerTest
|
||||||
$request = new Request();
|
$request = new Request();
|
||||||
$request = $request->withAttribute('room_id', $room->id);
|
$request = $request->withAttribute('room_id', $room->id);
|
||||||
|
|
||||||
$controller = new ShiftsController(new Response());
|
$controller = new ShiftsController(new Response(), $this->url);
|
||||||
|
|
||||||
$response = $controller->entriesByRoom($request);
|
$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->assertEquals(['application/json'], $response->getHeader('content-type'));
|
||||||
$this->assertJson($response->getContent());
|
$this->assertJson($response->getContent());
|
||||||
|
|
Loading…
Reference in New Issue