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
LoginAction
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
5
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
 __invoke
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
1
 validateInput
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5namespace App\Action\Auth;
6
7use App\Domain\Auth\Service\AuthService;
8use App\Domain\Exception\BadRequestException;
9use App\Renderer\JsonRenderer;
10use App\Support\Row;
11use Psr\Http\Message\ResponseInterface;
12use Psr\Http\Message\ServerRequestInterface;
13
14use function sprintf;
15
16/**
17 * Login user.
18 *
19 * POST /api/auth/login
20 */
21final readonly class LoginAction
22{
23    public function __construct(
24        private AuthService $authService,
25        private JsonRenderer $renderer
26    ) {}
27
28    public function __invoke(
29        ServerRequestInterface $request,
30        ResponseInterface $response,
31    ): ResponseInterface {
32        // Get request data
33        $data = (array)$request->getParsedBody();
34
35        // Validate required fields
36        $this->validateInput($data);
37
38        // Get IP and User Agent for session tracking
39        $serverParams = $request->getServerParams();
40        $ipAddress = Row::nullableString($serverParams, 'REMOTE_ADDR');
41        $userAgent = $request->getHeaderLine('User-Agent');
42
43        // Login
44        $result = $this->authService->login(
45            username: Row::string($data, 'username'),
46            password: Row::string($data, 'password'),
47            ipAddress: $ipAddress,
48            userAgent: $userAgent,
49        );
50
51        $user = $result['user'];
52        $tokens = $result['tokens'];
53
54        return $this->renderer->json($response, [
55            'success' => true,
56            'message' => 'Login successful',
57            'data' => [
58                'user' => [
59                    'userId' => $user->userId,
60                    'username' => $user->username,
61                    'email' => $user->email,
62                    'role' => $user->role,
63                ],
64                'accessToken' => $tokens->accessToken,
65                'refreshToken' => $tokens->refreshToken,
66                'tokenType' => $tokens->tokenType,
67                'expiresIn' => $tokens->expiresIn,
68            ],
69        ]);
70    }
71
72    /**
73     * Validate required input fields.
74     *
75     * @param array<mixed> $data
76     */
77    private function validateInput(array $data): void
78    {
79        $required = ['username', 'password'];
80
81        foreach ($required as $field) {
82            if (empty($data[$field])) {
83                throw new BadRequestException(
84                    sprintf('Missing required field: %s', $field),
85                );
86            }
87        }
88    }
89}