Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ApiRouteHandler
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
15
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 process
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 processApi
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Middleware;
6
7use Engelsystem\Exceptions\Handler;
8use Engelsystem\Http\Exceptions\HttpException;
9use Engelsystem\Http\Request;
10use Engelsystem\Http\Response;
11use Illuminate\Database\Eloquent\ModelNotFoundException;
12use Illuminate\Support\Str;
13use Nyholm\Psr7\Stream;
14use Nyholm\Psr7\Uri;
15use Psr\Http\Message\ResponseInterface;
16use Psr\Http\Message\ServerRequestInterface;
17use Psr\Http\Server\MiddlewareInterface;
18use Psr\Http\Server\RequestHandlerInterface;
19use Throwable;
20
21class 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}