Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
UserProfileApiController
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
5 / 5
17
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
 getProfile
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 changePassword
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
7
 updateAvatar
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 resolveAuthUserId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\User\Presentation\Api;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Modules\User\Application\Service\UserProfileService;
12use App\Shared\Infrastructure\Http\ApiResponseTrait;
13use Nyholm\Psr7\Factory\Psr17Factory;
14use Psr\Http\Message\ResponseInterface;
15use Psr\Http\Message\ServerRequestInterface;
16use Throwable;
17use Yiisoft\Session\SessionInterface;
18use Yiisoft\User\CurrentUser;
19
20/**
21 * REST API Controller for User Profile Management.
22 *
23 * Provides endpoints for retrieving profile details and executing secure password updates.
24 *
25 * @package App\Modules\User\Presentation\Api
26 */
27final readonly class UserProfileApiController
28{
29    use ApiResponseTrait;
30
31    /**
32     * UserProfileApiController constructor.
33     *
34     * @param UserProfileService $profileService User profile application service.
35     * @param SessionInterface $session User session service.
36     * @param CurrentUser $currentUser Current user identity.
37     * @param Psr17Factory $psr17Factory PSR-17 factory.
38     */
39    public function __construct(
40        private UserProfileService $profileService,
41        private SessionInterface $session,
42        private CurrentUser $currentUser,
43        private Psr17Factory $psr17Factory
44    ) {
45    }
46
47    /**
48     * Returns profile data for currently authenticated user.
49     *
50     * @param ServerRequestInterface $request HTTP server request.
51     * @return ResponseInterface JSON API response.
52     */
53    public function getProfile(ServerRequestInterface $request): ResponseInterface
54    {
55        $userId = $this->resolveAuthUserId($request);
56        if ($userId <= 0) {
57            return $this->jsonResponse($this->psr17Factory, ['error' => 'Unauthorized'], 401);
58        }
59
60        try {
61            $profile = $this->profileService->getProfile($userId);
62            return $this->jsonResponse($this->psr17Factory, ['data' => $profile], 200);
63        } catch (Throwable $e) {
64            return $this->jsonResponse($this->psr17Factory, ['error' => $e->getMessage()], 404);
65        }
66    }
67
68    /**
69     * Changes password for currently authenticated user.
70     *
71     * @param ServerRequestInterface $request HTTP server request.
72     * @return ResponseInterface JSON API response.
73     */
74    public function changePassword(ServerRequestInterface $request): ResponseInterface
75    {
76        $userId = $this->resolveAuthUserId($request);
77        if ($userId <= 0) {
78            return $this->jsonResponse($this->psr17Factory, ['error' => 'Unauthorized'], 401);
79        }
80
81        $payload = $this->parseJsonBody($request);
82        $currentPassword = (string)($payload['current_password'] ?? '');
83        $newPassword = (string)($payload['new_password'] ?? '');
84        $confirmPassword = (string)($payload['confirm_password'] ?? '');
85
86        if ($currentPassword === '' || $newPassword === '' || $confirmPassword === '') {
87            return $this->jsonResponse($this->psr17Factory, ['error' => 'All password fields are required.'], 422);
88        }
89
90        $result = $this->profileService->changePassword($userId, $currentPassword, $newPassword, $confirmPassword);
91        $status = $result['success'] ? 200 : 400;
92        $body = $result['success'] ? ['message' => $result['message']] : ['error' => $result['message']];
93
94        return $this->jsonResponse($this->psr17Factory, $body, $status);
95    }
96
97    /**
98     * Updates profile avatar for currently authenticated user.
99     *
100     * @param ServerRequestInterface $request HTTP server request.
101     * @return ResponseInterface JSON API response.
102     */
103    public function updateAvatar(ServerRequestInterface $request): ResponseInterface
104    {
105        $userId = $this->resolveAuthUserId($request);
106        if ($userId <= 0) {
107            return $this->jsonResponse($this->psr17Factory, ['error' => 'Unauthorized'], 401);
108        }
109
110        $payload = $this->parseJsonBody($request);
111        $avatarUrl = (string)($payload['avatar_url'] ?? '');
112
113        if ($avatarUrl === '') {
114            return $this->jsonResponse($this->psr17Factory, ['error' => 'Avatar URL is required.'], 422);
115        }
116
117        $result = $this->profileService->updateAvatar($userId, $avatarUrl);
118        $status = $result['success'] ? 200 : 400;
119        $body = $result['success']
120            ? ['message' => $result['message'], 'avatar_url' => $avatarUrl]
121            : ['error' => $result['message']];
122
123        return $this->jsonResponse($this->psr17Factory, $body, $status);
124    }
125
126    /**
127     * Resolves authenticated user ID from session or identity.
128     */
129    private function resolveAuthUserId(ServerRequestInterface $request): int
130    {
131        return $this->resolveCurrentUserId($request, $this->session, $this->currentUser);
132    }
133}