Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.30% covered (warning)
87.30%
55 / 63
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
GetLogsEndpoint
87.30% covered (warning)
87.30%
55 / 63
75.00% covered (warning)
75.00%
3 / 4
14.40
0.00% covered (danger)
0.00%
0 / 1
 configure
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 handle
77.78% covered (warning)
77.78%
28 / 36
0.00% covered (danger)
0.00%
0 / 1
7.54
 serializePageToken
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 deserializePageToken
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace Olz\Apps\Logs\Endpoints;
4
5use Olz\Api\OlzTypedEndpoint;
6use Olz\Apps\Logs\Utils\GzLogFile;
7use Olz\Apps\Logs\Utils\HybridLogFile;
8use Olz\Apps\Logs\Utils\LineLocation;
9use Olz\Apps\Logs\Utils\LogsDefinitions;
10use Olz\Apps\Logs\Utils\PlainLogFile;
11use PhpTypeScriptApi\HttpError;
12use PhpTypeScriptApi\PhpStan\IsoDateTime;
13
14/**
15 * @phpstan-type OlzLogLevel 'debug'|'info'|'notice'|'warning'|'error'|'critical'|'alert'|'emergency'
16 * @phpstan-type OlzLogsQuery array{
17 *   channel: string,
18 *   targetDate?: ?IsoDateTime,
19 *   firstDate?: ?IsoDateTime,
20 *   lastDate?: ?IsoDateTime,
21 *   minLogLevel?: ?OlzLogLevel,
22 *   textSearch?: ?string,
23 *   pageToken?: ?string,
24 * }
25 *
26 * @extends OlzTypedEndpoint<
27 *   array{query: OlzLogsQuery},
28 *   array{content: array<string>, pagination: array{previous: ?string, next: ?string}},
29 * >
30 */
31class GetLogsEndpoint extends OlzTypedEndpoint {
32    public function configure(): void {
33        parent::configure();
34        $this->phpStanUtils->registerApiObject(IsoDateTime::class);
35    }
36
37    protected function handle(mixed $input): mixed {
38        if (!$this->authUtils()->hasPermission('all')) {
39            throw new HttpError(403, "Kein Zugriff!");
40        }
41
42        $user = $this->authUtils()->getCurrentUser();
43        $this->log()->info("Logs access by {$user?->getUsername()}.");
44
45        $channel = null;
46        foreach (LogsDefinitions::getLogsChannels() as $current_channel) {
47            if ($current_channel::getId() === $input['query']['channel']) {
48                $channel = new $current_channel();
49            }
50        }
51        if (!$channel) {
52            throw new HttpError(404, "Channel not found");
53        }
54        $channel->setEnvUtils($this->envUtils());
55        $channel->setLog($this->log());
56
57        $query = $input['query'];
58        $page_token = $query['pageToken'] ?? null;
59        $date_time = $query['targetDate'] ?? null;
60        $result = null;
61        if ($page_token) {
62            $deserialized = $this->deserializePageToken($page_token);
63            $result = $channel->continueReading(
64                $deserialized['lineLocation'],
65                $deserialized['mode'],
66                $query,
67            );
68        } elseif ($date_time) {
69            $result = $channel->readAroundDateTime(
70                new \DateTime($date_time),
71                $query,
72            );
73        } else {
74            throw new HttpError(400, "Need to provide targetDate or pageToken");
75        }
76
77        return [
78            'content' => $result->lines,
79            'pagination' => [
80                // TODO: Encrypt!
81                'previous' => $this->serializePageToken($result->previous, 'previous'),
82                'next' => $this->serializePageToken($result->next, 'next'),
83            ],
84        ];
85    }
86
87    protected function serializePageToken(
88        ?LineLocation $line_location,
89        ?string $mode,
90    ): ?string {
91        if (!$line_location) {
92            return null;
93        }
94        return json_encode([
95            'logFile' => $line_location->logFile->serialize(),
96            'lineNumber' => $line_location->lineNumber,
97            'comparison' => $line_location->comparison,
98            'mode' => $mode,
99        ]) ?: null;
100    }
101
102    /** @return array{lineLocation: LineLocation, mode: string} */
103    protected function deserializePageToken(
104        string $serialized,
105    ): array {
106        $data = json_decode($serialized, true);
107        $log_file_classes = [
108            HybridLogFile::class,
109            GzLogFile::class,
110            PlainLogFile::class,
111        ];
112        $log_file = null;
113        foreach ($log_file_classes as $log_file_class) {
114            if (!$log_file) {
115                $log_file = $log_file_class::deserialize($data['logFile']);
116            }
117        }
118        $this->generalUtils()->checkNotNull($log_file, "No log file: {$data['logFile']}");
119        $line_location = new LineLocation($log_file, $data['lineNumber'], $data['comparison']);
120        $mode = $data['mode'];
121        return [
122            'lineLocation' => $line_location,
123            'mode' => $mode,
124        ];
125    }
126}