Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Tokens
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
5 / 5
13
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
 index
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 store
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 revoke
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 page
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Controller\Panel;
6
7use Celema\Container\Container;
8use Celema\Core\Factory\Factory;
9use Celema\Core\Request;
10use Celema\Core\Response;
11use Cosray\Config;
12use Cosray\User\Tokens as TokenStore;
13use DateTimeImmutable;
14use Throwable;
15use ValueError;
16
17/** The API tokens of the signed-in user, who manages only their own. */
18final class Tokens extends Panel
19{
20    protected const string AREA = 'profile';
21
22    public function __construct(
23        Config $config,
24        Container $container,
25        Request $request,
26        private readonly TokenStore $tokens,
27    ) {
28        parent::__construct($config, $container, $request);
29    }
30
31    /** @return array<string, mixed> */
32    public function index(): array
33    {
34        return $this->page();
35    }
36
37    /** @return array<string, mixed> */
38    public function store(): array
39    {
40        $data = $this->formData();
41        $name = is_string($data['name'] ?? null) ? trim($data['name']) : '';
42        $expires = is_string($data['expires'] ?? null) ? trim($data['expires']) : '';
43
44        try {
45            $expiry = $expires === ''
46                ? null
47                : new DateTimeImmutable($expires . ' 23:59:59', $this->config->app->timezone);
48        } catch (Throwable) {
49            return $this->page(error: __('tokens:error-expires'), name: $name);
50        }
51
52        try {
53            $created = $this->tokens->create($this->currentUser(), $name, $this->actor(), $expiry);
54        } catch (ValueError) {
55            return $this->page(
56                error: $expiry === null ? __('tokens:error-name') : __('tokens:error-invalid'),
57                name: $name,
58            );
59        }
60
61        return $this->page(created: $created['token']);
62    }
63
64    public function revoke(Factory $factory, string $uid): Response
65    {
66        $this->tokens->revoke($this->currentUser(), $uid);
67
68        return Response::create($factory)->redirect($this->panelPath() . '/profile/tokens', 303);
69    }
70
71    /** @return array<string, mixed> */
72    private function page(?string $created = null, ?string $error = null, string $name = ''): array
73    {
74        $timezone = $this->config->app->timezone;
75        $date = static fn(?string $value): ?string => $value === null
76            ? null
77            : new DateTimeImmutable($value)
78                ->setTimezone($timezone)
79                ->format('Y-m-d H:i');
80
81        return $this->context([
82            'created' => $created,
83            'error' => $error,
84            'name' => $name,
85            'tokensUrl' => $this->panelPath() . '/profile/tokens',
86            'profileUrl' => $this->panelPath() . '/profile',
87            'tokens' => array_map(fn(array $entry): array => [
88                'uid' => $entry['uid'],
89                'name' => $entry['name'] !== '' ? $entry['name'] : __('tokens:unnamed'),
90                'created' => $date($entry['created']),
91                'expires' => $date($entry['expires']),
92                'used' => $date($entry['used']),
93                'revokeUrl' => $this->panelPath() . '/profile/tokens/' . rawurlencode($entry['uid']) . '/delete',
94            ], $this->tokens->list($this->currentUser())),
95        ]);
96    }
97}