diff --git a/src/Controllers/Api/AngelTypeController.php b/src/Controllers/Api/AngelTypeController.php index 46314361..ed774770 100644 --- a/src/Controllers/Api/AngelTypeController.php +++ b/src/Controllers/Api/AngelTypeController.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Api; +use Engelsystem\Controllers\Api\Resources\AngelTypeResource; +use Engelsystem\Controllers\Api\Resources\UserAngelTypeResource; use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Models\AngelType; @@ -15,13 +17,9 @@ class AngelTypeController extends ApiController { $models = AngelType::query() ->orderBy('name') - ->get(['id', 'name', 'description']); + ->get(); - $models->map(function (AngelType $model): void { - $model->url = $this->getUrl($model); - }); - - $data = ['data' => $models]; + $data = ['data' => AngelTypeResource::collection($models)]; return $this->response ->withContent(json_encode($data)); } @@ -30,31 +28,9 @@ class AngelTypeController extends ApiController { $id = (int) $request->getAttribute('user_id'); $model = User::findOrFail($id); + $data = ['data' => UserAngelTypeResource::collection($model->userAngelTypes)]; - $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/src/Controllers/Api/ApiController.php b/src/Controllers/Api/ApiController.php index faf4fd81..7e4097d5 100644 --- a/src/Controllers/Api/ApiController.php +++ b/src/Controllers/Api/ApiController.php @@ -6,7 +6,6 @@ namespace Engelsystem\Controllers\Api; use Engelsystem\Controllers\BaseController; use Engelsystem\Http\Response; -use Engelsystem\Http\UrlGeneratorInterface; abstract class ApiController extends BaseController { @@ -14,7 +13,7 @@ abstract class ApiController extends BaseController 'api', ]; - public function __construct(protected Response $response, protected UrlGeneratorInterface $url) + public function __construct(protected Response $response) { $this->response = $this->response ->withHeader('content-type', 'application/json') diff --git a/src/Controllers/Api/LocationsController.php b/src/Controllers/Api/LocationsController.php index b27f6319..9a9c4667 100644 --- a/src/Controllers/Api/LocationsController.php +++ b/src/Controllers/Api/LocationsController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Api; +use Engelsystem\Controllers\Api\Resources\LocationResource; use Engelsystem\Http\Response; use Engelsystem\Models\Location; @@ -13,13 +14,9 @@ class LocationsController extends ApiController { $models = Location::query() ->orderBy('name') - ->get(['id', 'name']); + ->get(); - $models->map(function (Location $model): void { - $model->url = $this->url->to('/locations', ['action' => 'view', 'location_id' => $model->id]); - }); - - $data = ['data' => $models]; + $data = ['data' => LocationResource::collection($models)]; return $this->response ->withContent(json_encode($data)); } diff --git a/src/Controllers/Api/NewsController.php b/src/Controllers/Api/NewsController.php index 79b23c1c..4ef26d08 100644 --- a/src/Controllers/Api/NewsController.php +++ b/src/Controllers/Api/NewsController.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Api; +use Engelsystem\Controllers\Api\Resources\NewsResource; use Engelsystem\Http\Response; use Engelsystem\Models\News; @@ -14,13 +15,9 @@ class NewsController extends ApiController $models = News::query() ->orderByDesc('updated_at') ->orderByDesc('created_at') - ->get(['id', 'title', 'text', 'is_meeting', 'is_pinned', 'is_highlighted', 'created_at', 'updated_at']); + ->get(); - $models->map(function (News $model): void { - $model->url = $this->url->to('/news/' . $model->id); - }); - - $data = ['data' => $models]; + $data = ['data' => NewsResource::collection($models)]; return $this->response ->withContent(json_encode($data)); } diff --git a/src/Controllers/Api/Resources/AngelTypeResource.php b/src/Controllers/Api/Resources/AngelTypeResource.php new file mode 100644 index 00000000..2b37f9f3 --- /dev/null +++ b/src/Controllers/Api/Resources/AngelTypeResource.php @@ -0,0 +1,18 @@ + $this->model->id, + 'name' => $this->model->name, + 'description' => $this->model->description, + 'url' => url('/angeltypes', ['action' => 'view', 'angeltype_id' => $this->model->id]), + ]; + } +} diff --git a/src/Controllers/Api/Resources/BasicResource.php b/src/Controllers/Api/Resources/BasicResource.php new file mode 100644 index 00000000..b801a86c --- /dev/null +++ b/src/Controllers/Api/Resources/BasicResource.php @@ -0,0 +1,49 @@ +add(new static($item)); + } + return $collection; + } + + public function toArray(): array + { + return $this->model->toArray(); + } + + /** + * @param int $options + */ + public function toJson($options = 0): string // phpcs:ignore + { + return json_encode($this->toArray(), $options); + } + + public function __toString(): string + { + return $this->toJson(); + } +} diff --git a/src/Controllers/Api/Resources/LocationResource.php b/src/Controllers/Api/Resources/LocationResource.php new file mode 100644 index 00000000..e05bcc24 --- /dev/null +++ b/src/Controllers/Api/Resources/LocationResource.php @@ -0,0 +1,17 @@ + $this->model->id, + 'name' => $this->model->name, + 'url' => url('/locations', ['action' => 'view', 'location_id' => $this->model->id]), + ]; + } +} diff --git a/src/Controllers/Api/Resources/NewsResource.php b/src/Controllers/Api/Resources/NewsResource.php new file mode 100644 index 00000000..ef2a6d24 --- /dev/null +++ b/src/Controllers/Api/Resources/NewsResource.php @@ -0,0 +1,23 @@ + $this->model->id, + 'title' => $this->model->title, + 'text' => $this->model->text, + 'is_meeting' => $this->model->is_meeting, + 'is_pinned' => $this->model->is_pinned, + 'is_highlighted' => $this->model->is_highlighted, + 'created_at' => $this->model->created_at, + 'updated_at' => $this->model->updated_at, + 'url' => url('/news/' . $this->model->id), + ]; + } +} diff --git a/src/Controllers/Api/Resources/ShiftResource.php b/src/Controllers/Api/Resources/ShiftResource.php new file mode 100644 index 00000000..2087d415 --- /dev/null +++ b/src/Controllers/Api/Resources/ShiftResource.php @@ -0,0 +1,26 @@ + $this->model->id, + 'title' => $this->model->title, + 'description' => $this->model->description, + 'starts_at' => $this->model->start, + 'ends_at' => $this->model->end, + 'location' => $location instanceof Arrayable ? $location->toArray() : $location, + 'shift_type' => (new ShiftTypeResource($this->model->shiftType))->toArray(), + 'created_at' => $this->model->created_at, + 'updated_at' => $this->model->updated_at, + 'url' => url('/shifts', ['action' => 'view', 'shift_id' => $this->model->id]), + ]; + } +} diff --git a/src/Controllers/Api/Resources/ShiftTypeResource.php b/src/Controllers/Api/Resources/ShiftTypeResource.php new file mode 100644 index 00000000..b336a056 --- /dev/null +++ b/src/Controllers/Api/Resources/ShiftTypeResource.php @@ -0,0 +1,17 @@ + $this->model->id, + 'name' => $this->model->name, + 'description' => $this->model->description, + ]; + } +} diff --git a/src/Controllers/Api/Resources/ShiftWithEntriesResource.php b/src/Controllers/Api/Resources/ShiftWithEntriesResource.php new file mode 100644 index 00000000..1bd78c71 --- /dev/null +++ b/src/Controllers/Api/Resources/ShiftWithEntriesResource.php @@ -0,0 +1,18 @@ + $entries instanceof Arrayable ? $entries->toArray() : $entries, + ]; + } +} diff --git a/src/Controllers/Api/Resources/UserAngelTypeResource.php b/src/Controllers/Api/Resources/UserAngelTypeResource.php new file mode 100644 index 00000000..70da3332 --- /dev/null +++ b/src/Controllers/Api/Resources/UserAngelTypeResource.php @@ -0,0 +1,19 @@ + !$this->model->restricted + || $this->model->pivot->supporter + || $this->model->pivot->confirm_user_id, + 'supporter' => $this->model->pivot->supporter, + ]; + } +} diff --git a/src/Controllers/Api/Resources/UserResource.php b/src/Controllers/Api/Resources/UserResource.php new file mode 100644 index 00000000..8a89406c --- /dev/null +++ b/src/Controllers/Api/Resources/UserResource.php @@ -0,0 +1,21 @@ + $this->model->id, + 'name' => $this->model->name, + 'first_name' => $this->model->personalData->first_name, + 'last_name' => $this->model->personalData->last_name, + 'pronoun' => $this->model->personalData->pronoun, + 'contact' => $this->model->contact->only(['dect', 'mobile']), + 'url' => url('/users', ['action' => 'view', 'user_id' => $this->model->id]), + ]; + } +} diff --git a/src/Controllers/Api/ShiftsController.php b/src/Controllers/Api/ShiftsController.php index 082e8e5a..5252928b 100644 --- a/src/Controllers/Api/ShiftsController.php +++ b/src/Controllers/Api/ShiftsController.php @@ -4,6 +4,10 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Api; +use Engelsystem\Controllers\Api\Resources\AngelTypeResource; +use Engelsystem\Controllers\Api\Resources\LocationResource; +use Engelsystem\Controllers\Api\Resources\ShiftWithEntriesResource; +use Engelsystem\Controllers\Api\Resources\UserResource; use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Models\Location; @@ -39,53 +43,23 @@ class ShiftsController extends ApiController $entries = new Collection(); foreach ($neededAngelTypes as $neededAngelType) { - $users = []; - foreach ($neededAngelType->users ?? [] as $user) { - $users[] = [ - 'id' => $user->id, - 'name' => $user->name, - 'first_name' => $user->personalData->first_name, - '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]), - ]; - } + $users = UserResource::collection($neededAngelType->users ?? []); // Skip empty entries - if ($neededAngelType->count <= 0 && empty($users)) { + if ($neededAngelType->count <= 0 && $users->isEmpty()) { continue; } - $angelTypeData = $neededAngelType->angelType->only(['id', 'name', 'description']); - $angelTypeData['url'] = $this->url->to( - '/angeltypes', - ['action' => 'view', 'angeltype_id' => $neededAngelType->angelType->id] - ); - - $entries[] = [ + $angelTypeData = new AngelTypeResource($neededAngelType->angelType); + $entries[] = new Collection([ 'users' => $users, 'type' => $angelTypeData, 'needs' => $neededAngelType->count, - ]; + ]); } - $locationData = $location->only(['id', 'name']); - $locationData['url'] = $this->url->to('/locations', ['action' => 'view', 'location_id' => $location->id]); - - $shiftEntries[] = [ - 'id' => $shift->id, - 'title' => $shift->title, - 'description' => $shift->description, - 'starts_at' => $shift->start, - 'ends_at' => $shift->end, - 'location' => $locationData, - 'shift_type' => $shift->shiftType->only(['id', 'name', 'description']), - 'created_at' => $shift->created_at, - 'updated_at' => $shift->updated_at, - 'entries' => $entries, - 'url' => $this->url->to('/shifts', ['action' => 'view', 'shift_id' => $shift->id]), - ]; + $locationData = new LocationResource($location); + $shiftEntries[] = (new ShiftWithEntriesResource($shift))->toArray($locationData, $entries); } $data = ['data' => $shiftEntries]; diff --git a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php index 8b69bb27..dc161508 100644 --- a/tests/Unit/Controllers/Api/AngelTypeControllerTest.php +++ b/tests/Unit/Controllers/Api/AngelTypeControllerTest.php @@ -15,14 +15,13 @@ class AngelTypeControllerTest extends ApiBaseControllerTest { /** * @covers \Engelsystem\Controllers\Api\AngelTypeController::index - * @covers \Engelsystem\Controllers\Api\AngelTypeController::getUrl */ public function testIndex(): void { $this->initDatabase(); $items = AngelType::factory(3)->create(); - $controller = new AngelTypeController(new Response(), $this->url); + $controller = new AngelTypeController(new Response()); $response = $controller->index(); $this->validateApiResponse('/angeltypes', 'get', $response); @@ -39,6 +38,7 @@ class AngelTypeControllerTest extends ApiBaseControllerTest } /** * @covers \Engelsystem\Controllers\Api\AngelTypeController::ofUser + * @covers \Engelsystem\Controllers\Api\Resources\UserAngelTypeResource::toArray */ public function testOfUser(): void { @@ -46,7 +46,7 @@ class AngelTypeControllerTest extends ApiBaseControllerTest $user = User::factory()->create(); $items = UserAngelType::factory(3)->create(['user_id' => $user->id]); - $controller = new AngelTypeController(new Response(), $this->url); + $controller = new AngelTypeController(new Response()); $response = $controller->ofUser(new Request([], [], ['user_id' => $user->id])); $this->validateApiResponse('/users/{id}/angeltypes', 'get', $response); diff --git a/tests/Unit/Controllers/Api/ApiBaseControllerTest.php b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php index e157e0a1..b24479b9 100644 --- a/tests/Unit/Controllers/Api/ApiBaseControllerTest.php +++ b/tests/Unit/Controllers/Api/ApiBaseControllerTest.php @@ -15,7 +15,6 @@ 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 { @@ -41,6 +40,6 @@ abstract class ApiBaseControllerTest extends TestCase $query = http_build_query($params); return $path . ($query ? '?' . $query : ''); }); - $this->url = $url; + $this->app->instance('http.urlGenerator', $url); } } diff --git a/tests/Unit/Controllers/Api/ApiControllerTest.php b/tests/Unit/Controllers/Api/ApiControllerTest.php index fe01faa3..296d794c 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"}'), $this->url) extends ApiController { + $controller = new class (new Response('{"some":"json"}')) 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 8d12423e..69f79996 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(), $this->url); + $controller = new IndexController(new Response()); $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(), $this->url); + $controller = new IndexController(new Response()); $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(), $this->url); + $controller = new IndexController(new Response()); $response = $controller->options(); $this->assertEquals(200, $response->getStatusCode()); @@ -61,7 +61,7 @@ class IndexControllerTest extends ApiBaseControllerTest */ public function testNotFound(): void { - $controller = new IndexController(new Response(), $this->url); + $controller = new IndexController(new Response()); $response = $controller->notFound(); $this->assertEquals(404, $response->getStatusCode()); @@ -74,7 +74,7 @@ class IndexControllerTest extends ApiBaseControllerTest */ public function testNotImplemented(): void { - $controller = new IndexController(new Response(), $this->url); + $controller = new IndexController(new Response()); $response = $controller->notImplemented(); $this->assertEquals(405, $response->getStatusCode()); diff --git a/tests/Unit/Controllers/Api/LocationsControllerTest.php b/tests/Unit/Controllers/Api/LocationsControllerTest.php index 6cc6a6e7..951ec520 100644 --- a/tests/Unit/Controllers/Api/LocationsControllerTest.php +++ b/tests/Unit/Controllers/Api/LocationsControllerTest.php @@ -12,12 +12,13 @@ class LocationsControllerTest extends ApiBaseControllerTest { /** * @covers \Engelsystem\Controllers\Api\LocationsController::index + * @covers \Engelsystem\Controllers\Api\Resources\LocationResource::toArray */ public function testIndex(): void { $items = Location::factory(3)->create(); - $controller = new LocationsController(new Response(), $this->url); + $controller = new LocationsController(new Response()); $response = $controller->index(); $this->validateApiResponse('/locations', 'get', $response); diff --git a/tests/Unit/Controllers/Api/NewsControllerTest.php b/tests/Unit/Controllers/Api/NewsControllerTest.php index f85a8e51..b7ee50bd 100644 --- a/tests/Unit/Controllers/Api/NewsControllerTest.php +++ b/tests/Unit/Controllers/Api/NewsControllerTest.php @@ -12,12 +12,13 @@ class NewsControllerTest extends ApiBaseControllerTest { /** * @covers \Engelsystem\Controllers\Api\NewsController::index + * @covers \Engelsystem\Controllers\Api\Resources\NewsResource::toArray */ public function testIndex(): void { $items = News::factory(3)->create(); - $controller = new NewsController(new Response(), $this->url); + $controller = new NewsController(new Response()); $response = $controller->index(); $this->validateApiResponse('/news', 'get', $response); diff --git a/tests/Unit/Controllers/Api/Resources/BasicResourceTest.php b/tests/Unit/Controllers/Api/Resources/BasicResourceTest.php new file mode 100644 index 00000000..a1580f52 --- /dev/null +++ b/tests/Unit/Controllers/Api/Resources/BasicResourceTest.php @@ -0,0 +1,100 @@ +getModel(); + $resource = $this->getResource($model); + + $this->assertInstanceOf(Arrayable::class, $resource); + $this->assertEquals(['test' => 'value'], $resource->toArray()); + } + + /** + * @covers \Engelsystem\Controllers\Api\Resources\BasicResource::toJson + */ + public function testToJson(): void + { + $model = $this->getModel(); + $resource = $this->getResource($model); + + $this->assertInstanceOf(Jsonable::class, $resource); + $this->assertEquals('{"test":"value"}', $resource->toJson()); + } + + /** + * @covers \Engelsystem\Controllers\Api\Resources\BasicResource::toJson + */ + public function testToJsonOptions(): void + { + $resource = $this->getResource(new Collection()); + + $this->assertInstanceOf(Jsonable::class, $resource); + $this->assertEquals('{}', $resource->toJson(JSON_FORCE_OBJECT)); + } + + /** + * @covers \Engelsystem\Controllers\Api\Resources\BasicResource::__toString + */ + public function testToString(): void + { + $model = $this->getModel(); + $resource = $this->getResource($model); + + $this->assertInstanceOf(Stringable::class, $resource); + $this->assertEquals('{"test":"value"}', (string) $resource); + } + + /** + * @covers \Engelsystem\Controllers\Api\Resources\BasicResource::collection + */ + public function testCollection(): void + { + $resource = $this->getResource(new Collection()); + $modelA = $this->getModel(); + $modelB = $this->getModel()->setAttribute('test', 'B'); + $collection = $resource->collection([$modelA, $modelB]); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertCount(2, $collection); + + $this->assertInstanceOf(BasicResource::class, $collection->first()); + $this->assertEquals(['test' => 'value'], $collection->first()->toArray()); + + $this->assertInstanceOf(BasicResource::class, $collection->last()); + $this->assertEquals(['test' => 'B'], $collection->last()->toArray()); + } + + protected function getResource(BaseModel|Collection $model): BasicResource + { + return new class ($model) extends BasicResource { + }; + } + + + protected function getModel(): BaseModel + { + $model = new class extends BaseModel { + }; + $model->setAttribute('test', 'value'); + + return $model; + } +} diff --git a/tests/Unit/Controllers/Api/ShiftsControllerTest.php b/tests/Unit/Controllers/Api/ShiftsControllerTest.php index 05d837d3..6efc8c75 100644 --- a/tests/Unit/Controllers/Api/ShiftsControllerTest.php +++ b/tests/Unit/Controllers/Api/ShiftsControllerTest.php @@ -20,6 +20,11 @@ class ShiftsControllerTest extends ApiBaseControllerTest { /** * @covers \Engelsystem\Controllers\Api\ShiftsController::entriesByLocation + * @covers \Engelsystem\Controllers\Api\Resources\ShiftResource::toArray + * @covers \Engelsystem\Controllers\Api\Resources\ShiftTypeResource::toArray + * @covers \Engelsystem\Controllers\Api\Resources\ShiftWithEntriesResource::toArray + * @covers \Engelsystem\Controllers\Api\Resources\UserResource::toArray + * @covers \Engelsystem\Controllers\Api\Resources\AngelTypeResource::toArray * @covers \Engelsystem\Controllers\Api\ShiftsController::getNeededAngelTypes */ public function testEntriesByLocation(): void @@ -83,7 +88,7 @@ class ShiftsControllerTest extends ApiBaseControllerTest $request = new Request(); $request = $request->withAttribute('location_id', $location->id); - $controller = new ShiftsController(new Response(), $this->url); + $controller = new ShiftsController(new Response()); $response = $controller->entriesByLocation($request); $this->validateApiResponse('/locations/{id}/shifts', 'get', $response);