API helper classes
This commit is contained in:
26
composer.json
Normal file
26
composer.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "provm/api",
|
||||
"version": "1.0.0",
|
||||
"type": "library",
|
||||
"require": {
|
||||
"psr/http-message": "*",
|
||||
"slim/slim": "^4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "*"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Aldarien",
|
||||
"email": "aldarien85@gmail.com"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ProVM\\": "src/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
}
|
||||
}
|
17
src/Concept/API/Controller.php
Normal file
17
src/Concept/API/Controller.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
namespace ProVM\Concept\API;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
interface Controller
|
||||
{
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||
public function get(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||
public function getMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||
public function add(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||
public function edit(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||
public function editMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||
public function delete(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||
public function deleteMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||
}
|
13
src/Exception/Authorization/MissingBearer.php
Normal file
13
src/Exception/Authorization/MissingBearer.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Exception\Authorization;
|
||||
|
||||
use Throwable;
|
||||
use Exception;
|
||||
|
||||
class MissingBearer extends Exception
|
||||
{
|
||||
public function __construct(Throwable $previous = null)
|
||||
{
|
||||
parent::__construct('Missing Bearer token', 401, $previous);
|
||||
}
|
||||
}
|
13
src/Exception/Authorization/MissingHeader.php
Normal file
13
src/Exception/Authorization/MissingHeader.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Exception\Authorization;
|
||||
|
||||
use Throwable;
|
||||
use Exception;
|
||||
|
||||
class MissingHeader extends Exception
|
||||
{
|
||||
public function __construct(Throwable $previous = null)
|
||||
{
|
||||
parent::__construct('No Authorization header found', 401, $previous);
|
||||
}
|
||||
}
|
13
src/Exception/Authorization/MissingToken.php
Normal file
13
src/Exception/Authorization/MissingToken.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
namespace ProVM\Exception\Authorization;
|
||||
|
||||
use Throwable;
|
||||
use Exception;
|
||||
|
||||
class MissingToken extends Exception
|
||||
{
|
||||
public function __construct(string $placement, Throwable $previous)
|
||||
{
|
||||
parent::__construct("No token found in {$placement}", 401, $previous);
|
||||
}
|
||||
}
|
128
src/Ideal/API/Controller.php
Normal file
128
src/Ideal/API/Controller.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
namespace ProVM\Ideal\API;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use ProVM\Concept;
|
||||
use ProVM\Reinforce\Controller\Json;
|
||||
|
||||
abstract class Controller implements Concept\API\Controller
|
||||
{
|
||||
use Json;
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$output = [
|
||||
$this->getPlural() => $this->getService()->getAll()
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function get(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||
{
|
||||
$output = [
|
||||
'id' => $id,
|
||||
$this->getSingular() => $this->getService()->getById($id)
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function getMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$output = [
|
||||
'input' => $input,
|
||||
$this->getPlural() => []
|
||||
];
|
||||
if (isset($input[$this->getPlural()])) {
|
||||
if (!is_array($input[$this->getPlural()])) {
|
||||
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||
}
|
||||
foreach ($input[$this->getPlural()] as $id) {
|
||||
$output[$this->getPlural()][$id] = $this->getService()->getById($id);
|
||||
}
|
||||
}
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function add(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$output = [
|
||||
'input' => $input,
|
||||
$this->getPlural() => []
|
||||
];
|
||||
if (isset($input[$this->getPlural()])) {
|
||||
if (!is_array($input[$this->getPlural()])) {
|
||||
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||
}
|
||||
foreach ($input[$this->getPlural()] as $data) {
|
||||
$output[$this->getPlural()] []= $this->getService()->add($data);
|
||||
}
|
||||
}
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function edit(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$obj = $this->getService()->getById($id);
|
||||
$output = [
|
||||
'id' => $id,
|
||||
'input' => $input,
|
||||
'old' => $obj,
|
||||
$this->getSingular() => $this->getService()->edit($obj, $input)
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function editMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$output = [
|
||||
'input' => $input,
|
||||
'old' => [],
|
||||
$this->getPlural() => []
|
||||
];
|
||||
if (isset($input[$this->getPlural()])) {
|
||||
if (!is_array($input[$this->getPlural()])) {
|
||||
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||
}
|
||||
foreach ($input[$this->getPlural()] as $id => $data) {
|
||||
$obj = $this->getService()->getById($id);
|
||||
$output['old'][$id]= $obj;
|
||||
$output[$this->getPlural()][$id]= $this->getService()->edit($obj, $data);
|
||||
}
|
||||
}
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function delete(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||
{
|
||||
$obj = $this->getService()->getById($id);
|
||||
$output = [
|
||||
'id' => $id,
|
||||
$this->getSingular() => $obj,
|
||||
'deleted' => $this->getService()->delete($obj)
|
||||
];
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
public function deleteMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$output = [
|
||||
'input' => $input,
|
||||
'old' => [],
|
||||
$this->getPlural() => []
|
||||
];
|
||||
if (isset($input[$this->getPlural()])) {
|
||||
if (!is_array($input[$this->getPlural()])) {
|
||||
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||
}
|
||||
foreach ($input[$this->getPlural()] as $id) {
|
||||
$obj = $this->getService()->getById($id);
|
||||
$output['old'][$id] = $obj;
|
||||
$output[$this->getPlural()][$id] = $this->getService()->delete($obj);
|
||||
}
|
||||
}
|
||||
return $this->withJson($response, $output);
|
||||
}
|
||||
|
||||
abstract protected function getService(): Concept\Model\Service;
|
||||
abstract protected function getSingular(): string;
|
||||
abstract protected function getPlural(): string;
|
||||
}
|
30
src/Middleware/Authorization.php
Normal file
30
src/Middleware/Authorization.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
namespace ProVM\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Http\Server\RequestHandlerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use ProVM\Exception\Authorization\MissingToken;
|
||||
use ProVM\Service;
|
||||
|
||||
class Authorization
|
||||
{
|
||||
public function __construct(protected ResponseFactoryInterface $responseFactory,
|
||||
protected Service\Authorization $authorizationService,
|
||||
protected LoggerInterface $logger) {}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try {
|
||||
if (!$this->authorizationService->isAuthorized($request)) {
|
||||
return $this->responseFactory->createResponse(403); // Forbidden
|
||||
}
|
||||
} catch (MissingToken $exception) {
|
||||
$this->logger->alert($exception, ['request' => $request]);
|
||||
return $this->responseFactory->createResponse(401); // Unathorized
|
||||
}
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
25
src/Middleware/NotFound.php
Normal file
25
src/Middleware/NotFound.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
namespace ProVM\Middleware;
|
||||
|
||||
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface, ResponseFactoryInterface};
|
||||
use Psr\Http\Server\{RequestHandlerInterface};
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
|
||||
class NotFound
|
||||
{
|
||||
public function __construct(protected ResponseFactoryInterface $responseFactory, protected LoggerInterface $logger) {}
|
||||
|
||||
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
try {
|
||||
return $handler->handle($request);
|
||||
} catch (HttpNotFoundException $exception) {
|
||||
$this->logger->error($exception);
|
||||
$response = $this->responseFactory->createResponse(404)
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
$response->getBody()->write(json_encode(['code' => 404, 'error' => 'Not Found']));
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
15
src/Reinforce/Controller/Json.php
Normal file
15
src/Reinforce/Controller/Json.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
namespace ProVM\Reinforce\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
trait Json
|
||||
{
|
||||
public function withJson(ResponseInterface $response, mixed $data = null, int $statusCode = 200): ResponseInterface
|
||||
{
|
||||
$response->getBody()->write(json_encode($data));
|
||||
return $response
|
||||
->withStatus($statusCode)
|
||||
->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
85
src/Service/Authorization.php
Normal file
85
src/Service/Authorization.php
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
namespace ProVM\Service;
|
||||
|
||||
use Exception;
|
||||
use ProVM\Exception\Authorization\MissingBearer;
|
||||
use ProVM\Exception\Authorization\MissingHeader;
|
||||
use ProVM\Exception\Authorization\MissingToken;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
class Authorization
|
||||
{
|
||||
public function __construct(protected string $token) {}
|
||||
|
||||
/**
|
||||
* @throws MissingToken
|
||||
*/
|
||||
public function isAuthorized(ServerRequestInterface $request): bool
|
||||
{
|
||||
$suppliedToken = $this->getToken($request);
|
||||
return ($suppliedToken === $this->token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingToken
|
||||
*/
|
||||
private function getToken(ServerRequestInterface $request): string
|
||||
{
|
||||
try {
|
||||
return $this->getHeaderToken($request);
|
||||
} catch (Exception $exception) {
|
||||
try {
|
||||
return $this->getBodyToken($request, $exception);
|
||||
} catch (Exception $exception) {
|
||||
try {
|
||||
return $this->getQueryToken($request, $exception);
|
||||
} catch (Exception $exception) {
|
||||
throw new MissingToken('request', $exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingHeader
|
||||
* @throws MissingBearer
|
||||
*/
|
||||
private function getHeaderToken(ServerRequestInterface $request): string
|
||||
{
|
||||
$headers = $request->getHeader('Authorization');
|
||||
if (empty($headers)) {
|
||||
throw new MissingHeader();
|
||||
}
|
||||
foreach ($headers as $header) {
|
||||
if (!str_starts_with($header, 'Bearer ')) {
|
||||
continue;
|
||||
}
|
||||
return substr($header, strlen('Bearer '));
|
||||
}
|
||||
throw new MissingBearer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingToken
|
||||
*/
|
||||
private function getBodyToken(ServerRequestInterface $request, Exception $previous): string
|
||||
{
|
||||
$body = $request->getParsedBody();
|
||||
if (empty($body['token'])) {
|
||||
throw new MissingToken('body', $previous);
|
||||
}
|
||||
return $body['token'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MissingToken
|
||||
*/
|
||||
private function getQueryToken(ServerRequestInterface $request, Exception $previous): string
|
||||
{
|
||||
$query = $request->getQueryParams();
|
||||
if (empty($query['token'])) {
|
||||
throw new MissingToken('query', $previous);
|
||||
}
|
||||
return $query['token'];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user