Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
AdminUpdateUserHandler
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
2 / 2
10
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
 handle
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
9
1<?php
2
3declare(strict_types=1);
4
5namespace App\Identity\Application\Command;
6
7use App\Identity\Application\AuthenticatedUserFinder;
8use App\Identity\Application\Query\AdminUserView;
9use App\Identity\Domain\Exceptions\AdminSelfAction;
10use App\Identity\Domain\Exceptions\EmailAlreadyInUse;
11use App\Identity\Domain\UserRepository;
12use App\Identity\Domain\ValueObject\Email;
13use App\Identity\Domain\ValueObject\UserId;
14use App\Identity\Domain\ValueObject\UserStatus;
15use App\Shared\Domain\ValueObject\Currency;
16use App\Shared\Infrastructure\Storage\SignedImageUrl;
17use InvalidArgumentException;
18
19final readonly class AdminUpdateUserHandler
20{
21    public function __construct(
22        private AuthenticatedUserFinder $finder,
23        private UserRepository $users,
24        private SignedImageUrl $imageUrls,
25    ) {
26    }
27
28    public function handle(AdminUpdateUserCommand $command): AdminUserView
29    {
30        $status = UserStatus::tryFrom($command->status)
31            ?? throw new InvalidArgumentException('Invalid status.');
32
33        $onSelf = $command->callerId === $command->targetId;
34
35        // Guard the two changes that would lock the admin out of the admin area.
36        // Editing your own profile fields is fine; only these two are blocked.
37        if ($onSelf && !$command->isAdmin) {
38            throw AdminSelfAction::cannotRevokeSelf();
39        }
40        if ($onSelf && $status === UserStatus::SUSPENDED) {
41            throw AdminSelfAction::cannotSuspendSelf();
42        }
43
44        $user = $this->finder->getOrFail(new UserId($command->targetId));
45        $email = new Email($command->email);
46
47        if (!$user->email()->equals($email) && $this->users->existsByEmail($email)) {
48            throw EmailAlreadyInUse::of($email);
49        }
50
51        // Admin does not edit birthDate — preserve what the user set for themselves.
52        $user->update(
53            $email,
54            new Currency($command->preferredCurrency),
55            $command->name,
56            $command->surnames,
57            $user->birthDate(),
58        );
59
60        $command->isAdmin ? $user->grantAdmin() : $user->revokeAdmin();
61        $status === UserStatus::SUSPENDED ? $user->suspend() : $user->activate();
62
63        $this->users->save($user);
64
65        return AdminUserView::fromUser($user, $this->imageUrls->for($user->avatarName()));
66    }
67}