Skip to content

Commit 3920a1f

Browse files
author
Julien Neuhart
committed
Add create / update / deleter user UI and backend logic
1 parent 8517db3 commit 3920a1f

33 files changed

+1188
-71
lines changed

src/api/src/Infrastructure/Security/Voter/UserVoter.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414

1515
final class UserVoter extends AppVoter
1616
{
17-
public const UPDATE_USER = 'UPDATE_USER';
1817
public const GET_USER = 'GET_USER';
18+
public const DELETE_USER = 'DELETE_USER';
1919

2020
/**
2121
* @param mixed $subject
@@ -26,8 +26,8 @@ protected function supports(string $attribute, $subject): bool
2626
! in_array(
2727
$attribute,
2828
[
29-
self::UPDATE_USER,
3029
self::GET_USER,
30+
self::DELETE_USER,
3131
]
3232
)
3333
) {
@@ -52,16 +52,24 @@ protected function voteOnAttribute(string $attribute, $subject, TokenInterface $
5252
assert($subject instanceof User);
5353

5454
switch ($attribute) {
55-
case self::UPDATE_USER:
5655
case self::GET_USER:
57-
// The administrator can update/get any user.
56+
// The administrator can get any user.
5857
if ($this->security->isGranted(Role::getSymfonyRole(Role::ADMINISTRATOR()))) {
5958
return true;
6059
}
6160

62-
// Other users may only update/get themselves.
61+
// Other users may only get themselves.
6362
return $user->getId() === $subject->getId();
6463

64+
case self::DELETE_USER:
65+
// Only administrators may delete a user...
66+
if (! $this->security->isGranted(Role::getSymfonyRole(Role::ADMINISTRATOR()))) {
67+
return false;
68+
}
69+
70+
// ...but only if it's not a self delete!
71+
return $user->getId() !== $subject->getId();
72+
6573
default:
6674
return false;
6775
}

src/api/src/UseCase/User/CreateUser.php

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,35 @@
77
use App\Domain\Dao\UserDao;
88
use App\Domain\Enum\Locale;
99
use App\Domain\Enum\Role;
10+
use App\Domain\Model\Storable\ProfilePicture;
1011
use App\Domain\Model\User;
1112
use App\Domain\Throwable\InvalidModel;
13+
use App\Domain\Throwable\InvalidStorable;
1214
use App\UseCase\User\ResetPassword\ResetPassword;
15+
use Psr\Http\Message\UploadedFileInterface;
1316
use TheCodingMachine\GraphQLite\Annotations\Logged;
1417
use TheCodingMachine\GraphQLite\Annotations\Mutation;
1518
use TheCodingMachine\GraphQLite\Annotations\Security;
1619

1720
final class CreateUser
1821
{
1922
private UserDao $userDao;
23+
private UpdateProfilePicture $updateProfilePicture;
2024
private ResetPassword $resetPassword;
2125

2226
public function __construct(
2327
UserDao $userDao,
28+
UpdateProfilePicture $updateProfilePicture,
2429
ResetPassword $resetPassword
2530
) {
26-
$this->userDao = $userDao;
27-
$this->resetPassword = $resetPassword;
31+
$this->userDao = $userDao;
32+
$this->updateProfilePicture = $updateProfilePicture;
33+
$this->resetPassword = $resetPassword;
2834
}
2935

3036
/**
3137
* @throws InvalidModel
38+
* @throws InvalidStorable
3239
*
3340
* @Mutation
3441
* @Logged
@@ -39,7 +46,35 @@ public function createUser(
3946
string $lastName,
4047
string $email,
4148
Locale $locale,
42-
Role $role
49+
Role $role,
50+
?UploadedFileInterface $profilePicture = null
51+
): User {
52+
$storable = null;
53+
if ($profilePicture !== null) {
54+
$storable = ProfilePicture::createFromUploadedFile($profilePicture);
55+
}
56+
57+
return $this->create(
58+
$firstName,
59+
$lastName,
60+
$email,
61+
$locale,
62+
$role,
63+
$storable
64+
);
65+
}
66+
67+
/**
68+
* @throws InvalidModel
69+
* @throws InvalidStorable
70+
*/
71+
public function create(
72+
string $firstName,
73+
string $lastName,
74+
string $email,
75+
Locale $locale,
76+
Role $role,
77+
?ProfilePicture $profilePicture = null
4378
): User {
4479
$user = new User(
4580
$firstName,
@@ -49,7 +84,20 @@ public function createUser(
4984
$role
5085
);
5186

52-
$this->userDao->save($user);
87+
if ($profilePicture === null) {
88+
$this->userDao->save($user);
89+
90+
return $user;
91+
}
92+
93+
$this->userDao->validate($user);
94+
$user = $this
95+
->updateProfilePicture
96+
->update(
97+
$user,
98+
$profilePicture
99+
);
100+
53101
$this->resetPassword->resetPassword($email);
54102

55103
return $user;

src/api/src/UseCase/User/DeleteUser.php

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,40 @@
66

77
use App\Domain\Dao\UserDao;
88
use App\Domain\Model\User;
9+
use App\Domain\Storage\ProfilePictureStorage;
910
use TheCodingMachine\GraphQLite\Annotations\Logged;
1011
use TheCodingMachine\GraphQLite\Annotations\Mutation;
1112
use TheCodingMachine\GraphQLite\Annotations\Security;
1213

1314
final class DeleteUser
1415
{
1516
private UserDao $userDao;
17+
private ProfilePictureStorage $profilePictureStorage;
1618

1719
public function __construct(
18-
UserDao $userDao
20+
UserDao $userDao,
21+
ProfilePictureStorage $profilePictureStorage
1922
) {
20-
$this->userDao = $userDao;
23+
$this->userDao = $userDao;
24+
$this->profilePictureStorage = $profilePictureStorage;
2125
}
2226

2327
/**
2428
* @Mutation
2529
* @Logged
26-
* @Security("is_granted('ROLE_ADMINISTRATOR')")
30+
* @Security("is_granted('DELETE_USER', user1)")
2731
*/
28-
public function deleteUser(User $user): bool
32+
public function deleteUser(User $user1): bool
2933
{
34+
// Remove profile picture (if any).
35+
$filename = $user1->getProfilePicture();
36+
if ($filename !== null) {
37+
$this->profilePictureStorage->delete($filename);
38+
}
39+
3040
// Cascade = true will also delete the reset
3141
// password token (if any).
32-
$this->userDao->delete($user, true);
42+
$this->userDao->delete($user1, true);
3343

3444
return true;
3545
}

src/api/src/UseCase/User/GetUser.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ final class GetUser
1414
/**
1515
* @Query
1616
* @Logged
17-
* @Security("is_granted('GET_USER', user)")
17+
* @Security("is_granted('GET_USER', user1)")
1818
*/
19-
public function user(User $user): User
19+
public function user(User $user1): User
2020
{
2121
// GraphQLite black magic.
22-
return $user;
22+
return $user1;
2323
}
2424
}

src/api/src/UseCase/User/UpdateMyProfile.php renamed to src/api/src/UseCase/User/UpdateProfilePicture.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use TheCodingMachine\GraphQLite\Annotations\Logged;
1616
use TheCodingMachine\GraphQLite\Annotations\Mutation;
1717

18-
final class UpdateMyProfile
18+
final class UpdateProfilePicture
1919
{
2020
private UserDao $userDao;
2121
private ProfilePictureStorage $profilePictureStorage;
@@ -36,7 +36,7 @@ public function __construct(
3636
* @Logged
3737
* @InjectUser(for="$user")
3838
*/
39-
public function updateMyProfile(
39+
public function updateProfilePicture(
4040
User $user,
4141
?UploadedFileInterface $profilePicture = null
4242
): User {
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\UseCase\User;
6+
7+
use App\Domain\Dao\UserDao;
8+
use App\Domain\Enum\Locale;
9+
use App\Domain\Enum\Role;
10+
use App\Domain\Model\Storable\ProfilePicture;
11+
use App\Domain\Model\User;
12+
use App\Domain\Throwable\InvalidModel;
13+
use App\Domain\Throwable\InvalidStorable;
14+
use Psr\Http\Message\UploadedFileInterface;
15+
use TheCodingMachine\GraphQLite\Annotations\Logged;
16+
use TheCodingMachine\GraphQLite\Annotations\Mutation;
17+
use TheCodingMachine\GraphQLite\Annotations\Security;
18+
19+
use function strval;
20+
21+
final class UpdateUser
22+
{
23+
private UserDao $userDao;
24+
private UpdateProfilePicture $updateProfilePicture;
25+
26+
public function __construct(
27+
UserDao $userDao,
28+
UpdateProfilePicture $updateProfilePicture
29+
) {
30+
$this->userDao = $userDao;
31+
$this->updateProfilePicture = $updateProfilePicture;
32+
}
33+
34+
/**
35+
* @throws InvalidModel
36+
* @throws InvalidStorable
37+
*
38+
* @Mutation
39+
* @Logged
40+
* @Security("is_granted('ROLE_ADMINISTRATOR')")
41+
*/
42+
public function updateUser(
43+
User $user,
44+
string $firstName,
45+
string $lastName,
46+
string $email,
47+
Locale $locale,
48+
Role $role,
49+
?UploadedFileInterface $profilePicture = null
50+
): User {
51+
$storable = null;
52+
if ($profilePicture !== null) {
53+
$storable = ProfilePicture::createFromUploadedFile($profilePicture);
54+
}
55+
56+
return $this->update(
57+
$user,
58+
$firstName,
59+
$lastName,
60+
$email,
61+
$locale,
62+
$role,
63+
$storable
64+
);
65+
}
66+
67+
/**
68+
* @throws InvalidModel
69+
* @throws InvalidStorable
70+
*/
71+
public function update(
72+
User $user,
73+
string $firstName,
74+
string $lastName,
75+
string $email,
76+
Locale $locale,
77+
Role $role,
78+
?ProfilePicture $profilePicture = null
79+
): User {
80+
$user->setFirstName($firstName);
81+
$user->setLastName($lastName);
82+
$user->setEmail($email);
83+
$user->setLocale(strval($locale));
84+
$user->setRole(strval($role));
85+
86+
if ($profilePicture === null) {
87+
$this->userDao->save($user);
88+
89+
return $user;
90+
}
91+
92+
$this->userDao->validate($user);
93+
94+
return $this
95+
->updateProfilePicture
96+
->update(
97+
$user,
98+
$profilePicture
99+
);
100+
}
101+
}

src/webapp/assets/css/overrides.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ body {
99
.card {
1010
box-shadow: 0 1px 2px 0 rgba(0,0,0,.1);
1111
}
12+
13+
.table td {
14+
vertical-align: middle !important;
15+
}

0 commit comments

Comments
 (0)