API: Split to multiple controllers, removed / from routes
This commit is contained in:
parent
ca0a69b17d
commit
b5d94971bc
|
@ -111,24 +111,24 @@ $route->addGroup(
|
||||||
$route->addGroup(
|
$route->addGroup(
|
||||||
'/api',
|
'/api',
|
||||||
function (RouteCollector $route): void {
|
function (RouteCollector $route): void {
|
||||||
$route->get('/', 'ApiController@index');
|
$route->get('', 'Api\IndexController@index');
|
||||||
|
|
||||||
$route->addGroup(
|
$route->addGroup(
|
||||||
'/v0-beta',
|
'/v0-beta',
|
||||||
function (RouteCollector $route): void {
|
function (RouteCollector $route): void {
|
||||||
$route->addRoute(['OPTIONS'], '[/{resource:.+}]', 'ApiController@options');
|
$route->addRoute(['OPTIONS'], '[/{resource:.+}]', 'Api\IndexController@options');
|
||||||
$route->get('/', 'ApiController@indexV0');
|
$route->get('', 'Api\IndexController@indexV0');
|
||||||
|
|
||||||
$route->get('/news', 'ApiController@news');
|
$route->get('/news', 'Api\NewsController@index');
|
||||||
|
|
||||||
$route->addRoute(
|
$route->addRoute(
|
||||||
['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'],
|
['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'],
|
||||||
'[/{resource:.+}]',
|
'/[{resource:.+}]',
|
||||||
'ApiController@notImplemented'
|
'Api\IndexController@notImplemented'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
$route->get('[/{resource:.+}]', 'ApiController@notImplemented');
|
$route->get('/[{resource:.+}]', 'Api\IndexController@notImplemented');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Controllers\BaseController;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
|
||||||
|
abstract class ApiController extends BaseController
|
||||||
|
{
|
||||||
|
public array $permissions = [
|
||||||
|
'api',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(protected Response $response)
|
||||||
|
{
|
||||||
|
$this->response = $this->response
|
||||||
|
->withHeader('content-type', 'application/json')
|
||||||
|
// Using * here to "skip" all other headers on browser requests
|
||||||
|
->withHeader('access-control-allow-origin', '*');
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,24 +2,17 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace Engelsystem\Controllers;
|
namespace Engelsystem\Controllers\Api;
|
||||||
|
|
||||||
use Engelsystem\Http\Response;
|
use Engelsystem\Http\Response;
|
||||||
use Engelsystem\Models\News;
|
|
||||||
|
|
||||||
class ApiController extends BaseController
|
class IndexController extends ApiController
|
||||||
{
|
{
|
||||||
public array $permissions = [
|
public array $permissions = [
|
||||||
'api',
|
'index' => 'api',
|
||||||
|
'indexV0' => 'api',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(protected Response $response)
|
|
||||||
{
|
|
||||||
$this->response = $this->response
|
|
||||||
->withHeader('content-type', 'application/json')
|
|
||||||
->withHeader('Access-Control-Allow-Origin', '*');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
return $this->response
|
return $this->response
|
||||||
|
@ -44,10 +37,11 @@ class ApiController extends BaseController
|
||||||
|
|
||||||
public function options(): Response
|
public function options(): Response
|
||||||
{
|
{
|
||||||
|
// Respond to browser preflight options requests
|
||||||
return $this->response
|
return $this->response
|
||||||
->setStatusCode(204)
|
->setStatusCode(204)
|
||||||
->withHeader('Allow', 'OPTIONS, HEAD, GET')
|
->withHeader('allow', 'OPTIONS, HEAD, GET')
|
||||||
->withHeader('Access-Control-Allow-Headers', 'Authorization');
|
->withHeader('access-control-allow-headers', 'Authorization');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function notImplemented(): Response
|
public function notImplemented(): Response
|
||||||
|
@ -56,16 +50,4 @@ class ApiController extends BaseController
|
||||||
->setStatusCode(501)
|
->setStatusCode(501)
|
||||||
->withContent(json_encode(['error' => 'Not implemented']));
|
->withContent(json_encode(['error' => 'Not implemented']));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function news(): Response
|
|
||||||
{
|
|
||||||
$news = 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];
|
|
||||||
return $this->response
|
|
||||||
->withContent(json_encode($data));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Models\News;
|
||||||
|
|
||||||
|
class NewsController extends ApiController
|
||||||
|
{
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
$news = 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];
|
||||||
|
return $this->response
|
||||||
|
->withContent(json_encode($data));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers\Api;
|
||||||
|
|
||||||
|
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 Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
abstract class ApiBaseControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
protected OpenApiResponseValidator $validator;
|
||||||
|
|
||||||
|
protected function validateApiResponse(string $path, string $method, ResponseInterface $response): void
|
||||||
|
{
|
||||||
|
$operation = new OpenApiAddress($path, $method);
|
||||||
|
$this->validator->validate($operation, $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
$this->initDatabase();
|
||||||
|
|
||||||
|
$openApiDefinition = $this->app->get('path.resources.api') . '/openapi.yml';
|
||||||
|
$this->validator = (new OpenApiValidatorBuilder())
|
||||||
|
->fromYamlFile($openApiDefinition)
|
||||||
|
->getResponseValidator();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Controllers\Api\ApiController;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
|
||||||
|
class ApiControllerTest extends ApiBaseControllerTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\ApiController::__construct
|
||||||
|
*/
|
||||||
|
public function testConstruct(): void
|
||||||
|
{
|
||||||
|
$controller = new class (new Response('{"some":"json"}')) extends ApiController {
|
||||||
|
public function getResponse(): Response
|
||||||
|
{
|
||||||
|
return $this->response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$response = $controller->getResponse();
|
||||||
|
|
||||||
|
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
|
||||||
|
$this->assertJson($response->getContent());
|
||||||
|
|
||||||
|
$this->assertEquals(['*'], $response->getHeader('access-control-allow-origin'));
|
||||||
|
$this->assertJson($response->getContent());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Controllers\Api\IndexController;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
|
||||||
|
class IndexControllerTest extends ApiBaseControllerTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\IndexController::__construct
|
||||||
|
* @covers \Engelsystem\Controllers\Api\IndexController::index
|
||||||
|
*/
|
||||||
|
public function testIndex(): void
|
||||||
|
{
|
||||||
|
$controller = new IndexController(new Response());
|
||||||
|
$response = $controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
|
||||||
|
$this->assertEquals(['*'], $response->getHeader('access-control-allow-origin'));
|
||||||
|
$this->assertJson($response->getContent());
|
||||||
|
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
$this->assertArrayHasKey('versions', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\IndexController::indexV0
|
||||||
|
*/
|
||||||
|
public function testIndexV0(): void
|
||||||
|
{
|
||||||
|
$controller = new IndexController(new Response());
|
||||||
|
$response = $controller->indexV0();
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertJson($response->getContent());
|
||||||
|
|
||||||
|
$data = json_decode($response->getContent(), true);
|
||||||
|
$this->assertArrayHasKey('version', $data);
|
||||||
|
$this->assertArrayHasKey('paths', $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\IndexController::options
|
||||||
|
*/
|
||||||
|
public function testOptions(): void
|
||||||
|
{
|
||||||
|
$controller = new IndexController(new Response());
|
||||||
|
$response = $controller->options();
|
||||||
|
|
||||||
|
$this->assertEquals(204, $response->getStatusCode());
|
||||||
|
$this->assertNotEmpty($response->getHeader('allow'));
|
||||||
|
$this->assertNotEmpty($response->getHeader('access-control-allow-headers'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\IndexController::notImplemented
|
||||||
|
*/
|
||||||
|
public function testNotImplemented(): void
|
||||||
|
{
|
||||||
|
$controller = new IndexController(new Response());
|
||||||
|
$response = $controller->notImplemented();
|
||||||
|
|
||||||
|
$this->assertEquals(501, $response->getStatusCode());
|
||||||
|
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
|
||||||
|
$this->assertJson($response->getContent());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Engelsystem\Test\Unit\Controllers\Api;
|
||||||
|
|
||||||
|
use Engelsystem\Controllers\Api\NewsController;
|
||||||
|
use Engelsystem\Http\Response;
|
||||||
|
use Engelsystem\Models\News;
|
||||||
|
|
||||||
|
class NewsControllerTest extends ApiBaseControllerTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @covers \Engelsystem\Controllers\Api\NewsController::index
|
||||||
|
*/
|
||||||
|
public function testIndex(): void
|
||||||
|
{
|
||||||
|
News::factory(3)->create();
|
||||||
|
|
||||||
|
$controller = new NewsController(new Response());
|
||||||
|
|
||||||
|
$response = $controller->index();
|
||||||
|
$this->validateApiResponse('/news', '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']);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,114 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Engelsystem\Test\Unit\Controllers;
|
|
||||||
|
|
||||||
use Engelsystem\Controllers\ApiController;
|
|
||||||
use Engelsystem\Http\Response;
|
|
||||||
use Engelsystem\Models\News;
|
|
||||||
use League\OpenAPIValidation\PSR7\OperationAddress as OpenApiAddress;
|
|
||||||
use League\OpenAPIValidation\PSR7\ResponseValidator as OpenApiResponseValidator;
|
|
||||||
use League\OpenAPIValidation\PSR7\ValidatorBuilder as OpenApiValidatorBuilder;
|
|
||||||
|
|
||||||
class ApiControllerTest extends ControllerTest
|
|
||||||
{
|
|
||||||
protected OpenApiResponseValidator $validator;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::__construct
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::index
|
|
||||||
*/
|
|
||||||
public function testIndex(): void
|
|
||||||
{
|
|
||||||
$controller = new ApiController(new Response());
|
|
||||||
|
|
||||||
$response = $controller->index();
|
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
|
||||||
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
|
|
||||||
$this->assertEquals(['*'], $response->getHeader('Access-Control-Allow-Origin'));
|
|
||||||
$this->assertJson($response->getContent());
|
|
||||||
|
|
||||||
$data = json_decode($response->getContent(), true);
|
|
||||||
$this->assertArrayHasKey('versions', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::indexV0
|
|
||||||
*/
|
|
||||||
public function testIndexV0(): void
|
|
||||||
{
|
|
||||||
$controller = new ApiController(new Response());
|
|
||||||
|
|
||||||
$response = $controller->indexV0();
|
|
||||||
|
|
||||||
$this->assertEquals(200, $response->getStatusCode());
|
|
||||||
$this->assertJson($response->getContent());
|
|
||||||
|
|
||||||
$data = json_decode($response->getContent(), true);
|
|
||||||
$this->assertArrayHasKey('version', $data);
|
|
||||||
$this->assertArrayHasKey('paths', $data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::options
|
|
||||||
*/
|
|
||||||
public function testOptions(): void
|
|
||||||
{
|
|
||||||
$controller = new ApiController(new Response());
|
|
||||||
|
|
||||||
$response = $controller->options();
|
|
||||||
|
|
||||||
$this->assertEquals(204, $response->getStatusCode());
|
|
||||||
$this->assertNotEmpty($response->getHeader('Allow'));
|
|
||||||
$this->assertNotEmpty($response->getHeader('Access-Control-Allow-Headers'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::notImplemented
|
|
||||||
*/
|
|
||||||
public function testNotImplemented(): void
|
|
||||||
{
|
|
||||||
$controller = new ApiController(new Response());
|
|
||||||
|
|
||||||
$response = $controller->notImplemented();
|
|
||||||
|
|
||||||
$this->assertEquals(501, $response->getStatusCode());
|
|
||||||
$this->assertEquals(['application/json'], $response->getHeader('content-type'));
|
|
||||||
$this->assertJson($response->getContent());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @covers \Engelsystem\Controllers\ApiController::news
|
|
||||||
*/
|
|
||||||
public function testNews(): void
|
|
||||||
{
|
|
||||||
$this->initDatabase();
|
|
||||||
News::factory(3)->create();
|
|
||||||
|
|
||||||
$controller = new ApiController(new Response());
|
|
||||||
|
|
||||||
$response = $controller->news();
|
|
||||||
|
|
||||||
$operation = new OpenApiAddress('/news', 'get');
|
|
||||||
$this->validator->validate($operation, $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']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
|
|
||||||
$openApiDefinition = $this->app->get('path.resources.api') . '/openapi.yml';
|
|
||||||
$this->validator = (new OpenApiValidatorBuilder())
|
|
||||||
->fromYamlFile($openApiDefinition)
|
|
||||||
->getResponseValidator();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue