Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
AuditTrailEndpoint
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
9
100.00% covered (success)
100.00%
1 / 1
 handle
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace LambdaTwelve\OneRecord\Server\Endpoint;
6
7use LambdaTwelve\OneRecord\Api\ActionRequest;
8use LambdaTwelve\OneRecord\Api\RequestStatus;
9use LambdaTwelve\OneRecord\JsonLd\Nodes;
10use LambdaTwelve\OneRecord\Server\Http\HttpException;
11use LambdaTwelve\OneRecord\Server\Http\Negotiated;
12use LambdaTwelve\OneRecord\Server\Http\Responder;
13use LambdaTwelve\OneRecord\Server\Spi\Action;
14use LambdaTwelve\OneRecord\Server\Spi\Agent;
15use LambdaTwelve\OneRecord\Server\Spi\AuditTrailQuery;
16use LambdaTwelve\OneRecord\Vocabulary\Generated\Api;
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19
20/**
21 * GET /logistics-objects/{id}/audit-trail: every change and verification
22 * request about the object, with the latest revision. Access follows the
23 * object unless the host's policy separates them (Action::ReadAuditTrail).
24 */
25final class AuditTrailEndpoint extends AbstractEndpoint
26{
27    public function handle(ServerRequestInterface $request, Agent $agent, Negotiated $negotiated, array $parameters): ResponseInterface
28    {
29        $stored = $this->requireObject($parameters['id'], $agent, Action::ReadAuditTrail);
30        $query = self::query($request);
31        $status = null;
32        if (isset($query['status']) && trim($query['status']) !== '') {
33            $status = RequestStatus::tryFromString($query['status']) ?? throw HttpException::invalidQuery(\sprintf('"%s" is not a request status.', $query['status']), 'status');
34        }
35        $trailQuery = new AuditTrailQuery(
36            isset($query['updated-from']) && trim($query['updated-from']) !== '' ? self::parseAt($query['updated-from']) : null,
37            isset($query['updated-to']) && trim($query['updated-to']) !== '' ? self::parseAt($query['updated-to']) : null,
38            $status,
39        );
40        $requests = $this->services->actionRequests->auditTrail($stored->object->iri, $trailQuery);
41
42        $document = [
43            '@context' => [...Nodes::context(), 'api:hasDatatype' => ['@type' => 'xsd:anyURI'], 'api:p' => ['@type' => 'xsd:anyURI'], 'api:hasProperty' => ['@type' => 'xsd:anyURI'], 'api:hasResource' => ['@type' => 'xsd:anyURI']],
44            '@id' => $stored->object->iri->value . '/audit-trail',
45            '@type' => 'api:AuditTrail',
46            'api:hasLatestRevision' => Nodes::positiveInteger($stored->latestRevision),
47        ];
48        $nodes = array_map(fn(ActionRequest $r): array => $r->node($negotiated->version), $requests);
49        if ($nodes !== []) {
50            $document['api:hasActionRequest'] = $nodes;
51        }
52        $lastModified = $stored->lastModified;
53        foreach ($requests as $actionRequest) {
54            $lastModified = max($lastModified, $actionRequest->lastModified());
55        }
56
57        return $this->services->responder->jsonLd(200, $document, $negotiated, Api::AuditTrail, ['Last-Modified' => Responder::httpDate($lastModified)], self::isHead($request));
58    }
59}