diff --git a/resources/api/openapi.yml b/resources/api/openapi.yml index ffb6466c..d896960b 100644 --- a/resources/api/openapi.yml +++ b/resources/api/openapi.yml @@ -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: [ ] diff --git a/src/Controllers/Api/AngelTypeController.php b/src/Controllers/Api/AngelTypeController.php index 5f851fd9..c2a022ff 100644 --- a/src/Controllers/Api/AngelTypeController.php +++ b/src/Controllers/Api/AngelTypeController.php @@ -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)); } diff --git a/src/Controllers/Api/ApiController.php b/src/Controllers/Api/ApiController.php index 7e4097d5..faf4fd81 100644 --- a/src/Controllers/Api/ApiController.php +++ b/src/Controllers/Api/ApiController.php @@ -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') diff --git a/src/Controllers/Api/NewsController.php b/src/Controllers/Api/NewsController.php index 983d6bea..79b23c1c 100644 --- a/src/Controllers/Api/NewsController.php +++ b/src/Controllers/Api/NewsController.php @@ -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)); } diff --git a/src/Controllers/Api/RoomsController.php b/src/Controllers/Api/RoomsController.php index a5338db5..b19a5dc1 100644 --- a/src/Controllers/Api/RoomsController.php +++ b/src/Controllers/Api/RoomsController.php @@ -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)); } diff --git a/src/Controllers/Api/ShiftsController.php b/src/Controllers/Api/ShiftsController.php index ebf834ee..57648d23 100644 --- a/src/Controllers/Api/ShiftsController.php +++ b/src/Controllers/Api/ShiftsController.php @@ -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]), ]; } diff --git a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php index 0b82c4bc..b6db952b 100644 --- a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php +++ b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php @@ -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); diff --git a/tests/Unit/Controllers/Api/ApiBaseControllerTest.php b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php index 9989e8c7..e157e0a1 100644 --- a/tests/Unit/Controllers/Api/ApiBaseControllerTest.php +++ b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php @@ -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; } } diff --git a/tests/Unit/Controllers/Api/ApiControllerTest.php b/tests/Unit/Controllers/Api/ApiControllerTest.php index 296d794c..fe01faa3 100644 --- a/tests/Unit/Controllers/Api/ApiControllerTest.php +++ b/tests/Unit/Controllers/Api/ApiControllerTest.php @@ -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; diff --git a/tests/Unit/Controllers/Api/IndexControllerTest.php b/tests/Unit/Controllers/Api/IndexControllerTest.php index d4749467..fe3772b4 100644 --- a/tests/Unit/Controllers/Api/IndexControllerTest.php +++ b/tests/Unit/Controllers/Api/IndexControllerTest.php @@ -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()); diff --git a/tests/Unit/Controllers/Api/NewsControllerTest.php b/tests/Unit/Controllers/Api/NewsControllerTest.php index feba4d54..1cd67e6b 100644 --- a/tests/Unit/Controllers/Api/NewsControllerTest.php +++ b/tests/Unit/Controllers/Api/NewsControllerTest.php @@ -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); diff --git a/tests/Unit/Controllers/Api/RoomsControllerTest.php b/tests/Unit/Controllers/Api/RoomsControllerTest.php index 6305e610..bf20ece8 100644 --- a/tests/Unit/Controllers/Api/RoomsControllerTest.php +++ b/tests/Unit/Controllers/Api/RoomsControllerTest.php @@ -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); diff --git a/tests/Unit/Controllers/Api/ShiftsControllerTest.php b/tests/Unit/Controllers/Api/ShiftsControllerTest.php index 44876a7d..d0315dd1 100644 --- a/tests/Unit/Controllers/Api/ShiftsControllerTest.php +++ b/tests/Unit/Controllers/Api/ShiftsControllerTest.php @@ -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());