Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
36 / 36 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
ApiRouteHandler | |
100.00% |
36 / 36 |
|
100.00% |
3 / 3 |
15 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
process | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
7 | |||
processApi | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
7 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Engelsystem\Middleware; |
6 | |
7 | use Engelsystem\Exceptions\Handler; |
8 | use Engelsystem\Http\Exceptions\HttpException; |
9 | use Engelsystem\Http\Request; |
10 | use Engelsystem\Http\Response; |
11 | use Illuminate\Database\Eloquent\ModelNotFoundException; |
12 | use Illuminate\Support\Str; |
13 | use Nyholm\Psr7\Stream; |
14 | use Nyholm\Psr7\Uri; |
15 | use Psr\Http\Message\ResponseInterface; |
16 | use Psr\Http\Message\ServerRequestInterface; |
17 | use Psr\Http\Server\MiddlewareInterface; |
18 | use Psr\Http\Server\RequestHandlerInterface; |
19 | use Throwable; |
20 | |
21 | class ApiRouteHandler implements MiddlewareInterface |
22 | { |
23 | public function __construct( |
24 | protected ?string $apiPrefix = '/api', |
25 | protected ?array $apiAccessiblePaths = [ |
26 | '/atom', |
27 | '/rss', |
28 | '/health', |
29 | '/ical', |
30 | '/metrics', |
31 | '/shifts-json-export', |
32 | '/stats', |
33 | ] |
34 | ) { |
35 | } |
36 | |
37 | /** |
38 | * Process the incoming request and handling API responses |
39 | */ |
40 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
41 | { |
42 | $path = (new Uri((string) $request->getUri()))->getPath(); |
43 | if ($request instanceof Request) { |
44 | $path = $request->getPathInfo(); |
45 | } |
46 | |
47 | $path = urldecode($path); |
48 | $isApi = $this->apiPrefix && (Str::startsWith($path, $this->apiPrefix . '/') || $path == $this->apiPrefix); |
49 | $isApiAccessible = $isApi || $this->apiAccessiblePaths && in_array($path, $this->apiAccessiblePaths); |
50 | $request = $request |
51 | ->withAttribute('route-api', $isApi) |
52 | ->withAttribute('route-api-accessible', $isApiAccessible); |
53 | |
54 | return $isApi ? $this->processApi($request, $handler) : $handler->handle($request); |
55 | } |
56 | |
57 | /** |
58 | * Process the API request by ensuring that JSON is returned |
59 | */ |
60 | protected function processApi(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface |
61 | { |
62 | try { |
63 | $response = $handler->handle($request); |
64 | } catch (ModelNotFoundException) { |
65 | $response = new Response('', 404); |
66 | $response->setContent($response->getReasonPhrase()); |
67 | } catch (HttpException $e) { |
68 | $response = new Response($e->getMessage(), $e->getStatusCode(), $e->getHeaders()); |
69 | $response->setContent($response->getContent() ?: $response->getReasonPhrase()); |
70 | } catch (Throwable $e) { |
71 | /** @var Handler $handler */ |
72 | $handler = app('error.handler'); |
73 | $handler->exceptionHandler($e, true); |
74 | $response = new Response('', 500); |
75 | $response->setContent($response->getReasonPhrase()); |
76 | } |
77 | |
78 | if (!Str::isJson((string) $response->getBody())) { |
79 | $content = (string) $response->getBody(); |
80 | $content = Stream::create(json_encode([ |
81 | 'message' => $content, |
82 | ])); |
83 | $response = $response |
84 | ->withHeader('content-type', 'application/json') |
85 | ->withBody($content); |
86 | } |
87 | |
88 | if (!$response->hasHeader('access-control-allow-origin')) { |
89 | $response = $response->withHeader('access-control-allow-origin', '*'); |
90 | } |
91 | |
92 | $eTag = md5((string) $response->getBody()); |
93 | $response->setEtag($eTag); |
94 | |
95 | return $response; |
96 | } |
97 | } |