Skip to content

Commit b23943f

Browse files
committed
feat: implement role synchronization in UserRecord and update UserRepository for role management
1 parent 6be3ebf commit b23943f

File tree

3 files changed

+188
-1
lines changed

3 files changed

+188
-1
lines changed

contexts/Authorization/Infrastructure/Records/UserRecord.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Illuminate\Database\Eloquent\Builder;
1616
use Illuminate\Database\Eloquent\Factories\Factory;
1717
use Illuminate\Support\Carbon;
18+
use Contexts\Authorization\Domain\UserIdentity\Models\RoleIdCollection;
19+
use Contexts\Authorization\Domain\Role\Models\RoleId;
1820

1921
/**
2022
* @property int $id
@@ -37,6 +39,11 @@ class UserRecord extends BaseModel
3739
2 => 'deleted',
3840
];
3941

42+
public function roles()
43+
{
44+
return $this->belongsToMany(RoleRecord::class, 'pivot_user_role', 'user_id', 'role_id');
45+
}
46+
4047
public static function mapStatusToDomain(int $status): UserStatus
4148
{
4249
if (! isset(self::STATUS_MAPPING[$status])) {
@@ -57,6 +64,9 @@ public static function mapStatusToRecord(UserStatus $status): int
5764

5865
public function toDomain(array $events = []): UserIdentity
5966
{
67+
$rolesIds = $this->roles()->get()->pluck('id')->toArray();
68+
$roleIdsCollection = new RoleIdCollection(array_map(fn ($id) => RoleId::fromInt($id), $rolesIds));
69+
6070
return UserIdentity::reconstitute(
6171
UserId::fromInt($this->id),
6272
new Email($this->email),
@@ -65,6 +75,7 @@ public function toDomain(array $events = []): UserIdentity
6575
self::mapStatusToDomain($this->status),
6676
$this->created_at->toImmutable(),
6777
$this->updated_at?->toImmutable(),
78+
$roleIdsCollection,
6879
events: $events
6980
);
7081
}

contexts/Authorization/Infrastructure/Repositories/UserRepository.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Contexts\Authorization\Infrastructure\Records\UserRecord;
1212
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
1313
use Illuminate\Database\Eloquent\ModelNotFoundException;
14+
use Contexts\Authorization\Domain\UserIdentity\Models\RoleIdCollection;
1415

1516
class UserRepository
1617
{
@@ -38,9 +39,13 @@ public function getById(UserId $userId): UserIdentity
3839
return $record->toDomain();
3940
}
4041

41-
public function update(UserIdentity $user): UserIdentity
42+
private function syncRoles(UserRecord $user, RoleIdCollection $roleIdCollection): void
4243
{
44+
$user->roles()->sync($roleIdCollection->getIdsArray());
45+
}
4346

47+
public function update(UserIdentity $user): UserIdentity
48+
{
4449
try {
4550
$record = UserRecord::findOrFail($user->getId()->getValue());
4651
} catch (ModelNotFoundException $e) {
@@ -54,6 +59,8 @@ public function update(UserIdentity $user): UserIdentity
5459
'created_at' => $user->getCreatedAt(),
5560
]);
5661

62+
$this->syncRoles($record, $user->getRoleIdCollection());
63+
5764
return $record->toDomain($user->getEvents());
5865
}
5966

contexts/Authorization/Tests/Feature/Infrastructure/Repositories/UserRepositoryTest.php

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
declare(strict_types=1);
44
use Carbon\CarbonImmutable;
5+
use Contexts\Authorization\Domain\Role\Models\RoleId;
56
use Contexts\Authorization\Domain\UserIdentity\Exceptions\UserNotFoundException;
67
use Contexts\Authorization\Domain\UserIdentity\Models\Email;
78
use Contexts\Authorization\Domain\UserIdentity\Models\Password;
9+
use Contexts\Authorization\Domain\UserIdentity\Models\RoleIdCollection;
810
use Contexts\Authorization\Domain\UserIdentity\Models\UserId;
911
use Contexts\Authorization\Domain\UserIdentity\Models\UserIdentity;
1012
use Contexts\Authorization\Domain\UserIdentity\Models\UserStatus;
13+
use Contexts\Authorization\Infrastructure\Records\RoleRecord;
1114
use Contexts\Authorization\Infrastructure\Records\UserRecord;
1215
use Contexts\Authorization\Infrastructure\Repositories\UserRepository;
1316

@@ -251,3 +254,169 @@
251254
expect($retrievedUser->getPassword()->verify('oldpassword123'))->toBeFalse();
252255
expect($retrievedUser->getPassword()->verify('newpassword123'))->toBeTrue();
253256
});
257+
258+
it('can sync user roles when updating user', function () {
259+
// Create a test user in the database
260+
$email = new Email('role-test@example.com');
261+
$password = Password::createFromPlainText('password123');
262+
$createdUser = UserIdentity::create(UserId::null(), $email, $password, 'Role Test User');
263+
$userRepository = new UserRepository();
264+
$savedUser = $userRepository->create($createdUser);
265+
266+
// Create test roles
267+
$role1 = RoleRecord::create(['name' => 'Editor', 'description' => 'Editor role']);
268+
$role2 = RoleRecord::create(['name' => 'Author', 'description' => 'Author role']);
269+
$role3 = RoleRecord::create(['name' => 'Admin', 'description' => 'Admin role']);
270+
271+
// Create a RoleIdCollection with the first two roles
272+
$roleIds = new RoleIdCollection([
273+
RoleId::fromInt($role1->id),
274+
RoleId::fromInt($role2->id)
275+
]);
276+
277+
// Retrieve the user and assign roles
278+
$retrievedUser = $userRepository->getById($savedUser->getId());
279+
$retrievedUser->syncRoles($roleIds);
280+
281+
// Update the user with the roles
282+
$updatedUser = $userRepository->update($retrievedUser);
283+
284+
// Verify the roles were assigned correctly - use query builder to avoid ambiguous column issue
285+
$userRoles = \DB::table('pivot_user_role')
286+
->where('user_id', $updatedUser->getId()->getValue())
287+
->pluck('role_id')
288+
->toArray();
289+
290+
expect(count($userRoles))->toBe(2);
291+
expect($userRoles)->toContain($role1->id, $role2->id);
292+
293+
// Now update with a different set of roles
294+
$newRoleIds = new RoleIdCollection([
295+
RoleId::fromInt($role2->id),
296+
RoleId::fromInt($role3->id)
297+
]);
298+
299+
$updatedUser->syncRoles($newRoleIds);
300+
$userRepository->update($updatedUser);
301+
302+
// Verify the roles were updated correctly
303+
$userRoles = \DB::table('pivot_user_role')
304+
->where('user_id', $updatedUser->getId()->getValue())
305+
->pluck('role_id')
306+
->toArray();
307+
308+
expect(count($userRoles))->toBe(2);
309+
expect($userRoles)->toContain($role2->id, $role3->id);
310+
expect($userRoles)->not->toContain($role1->id);
311+
});
312+
313+
it('can sync user roles to empty collection', function () {
314+
// Create a test user in the database
315+
$email = new Email('empty-roles@example.com');
316+
$password = Password::createFromPlainText('password123');
317+
$createdUser = UserIdentity::create(UserId::null(), $email, $password, 'No Roles User');
318+
$userRepository = new UserRepository();
319+
$savedUser = $userRepository->create($createdUser);
320+
321+
// Create roles and assign them
322+
$role1 = RoleRecord::create(['name' => 'Manager', 'description' => 'Manager role']);
323+
$role2 = RoleRecord::create(['name' => 'Staff', 'description' => 'Staff role']);
324+
325+
$roleIds = new RoleIdCollection([
326+
RoleId::fromInt($role1->id),
327+
RoleId::fromInt($role2->id)
328+
]);
329+
330+
// Assign roles and update
331+
$retrievedUser = $userRepository->getById($savedUser->getId());
332+
$retrievedUser->syncRoles($roleIds);
333+
$userRepository->update($retrievedUser);
334+
335+
// Verify roles were assigned
336+
$userRoles = \DB::table('pivot_user_role')
337+
->where('user_id', $retrievedUser->getId()->getValue())
338+
->count();
339+
expect($userRoles)->toBe(2);
340+
341+
// Now remove all roles
342+
$emptyRoleIds = new RoleIdCollection([]);
343+
$retrievedUser->syncRoles($emptyRoleIds);
344+
$userRepository->update($retrievedUser);
345+
346+
// Verify all roles were removed
347+
$userRoles = \DB::table('pivot_user_role')
348+
->where('user_id', $retrievedUser->getId()->getValue())
349+
->count();
350+
expect($userRoles)->toBe(0);
351+
});
352+
353+
it('preserves existing user roles when updating other attributes', function () {
354+
// Create a test user in the database
355+
$email = new Email('preserve-roles@example.com');
356+
$password = Password::createFromPlainText('password123');
357+
$createdUser = UserIdentity::create(UserId::null(), $email, $password, 'Preserve Roles User');
358+
$userRepository = new UserRepository();
359+
$savedUser = $userRepository->create($createdUser);
360+
361+
// Create roles and assign them
362+
$role1 = RoleRecord::create(['name' => 'Subscriber', 'description' => 'Subscriber role']);
363+
$role2 = RoleRecord::create(['name' => 'Member', 'description' => 'Member role']);
364+
365+
$roleIds = new RoleIdCollection([
366+
RoleId::fromInt($role1->id),
367+
RoleId::fromInt($role2->id)
368+
]);
369+
370+
// Assign roles
371+
$user = $userRepository->getById($savedUser->getId());
372+
$user->syncRoles($roleIds);
373+
$userRepository->update($user);
374+
375+
// Get fresh instance with roles loaded
376+
$user = $userRepository->getById($savedUser->getId());
377+
378+
// Update only the user's display name
379+
$user->modify(null, 'Updated Display Name', null);
380+
$userRepository->update($user);
381+
382+
// Verify roles are still present after the update
383+
$userRecord = UserRecord::find($user->getId()->getValue());
384+
expect($userRecord->display_name)->toBe('Updated Display Name');
385+
386+
$userRoles = \DB::table('pivot_user_role')
387+
->where('user_id', $user->getId()->getValue())
388+
->pluck('role_id')
389+
->toArray();
390+
391+
expect(count($userRoles))->toBe(2);
392+
expect($userRoles)->toContain($role1->id, $role2->id);
393+
});
394+
395+
it('updates roles correctly even with empty initial role collection', function () {
396+
// Create a test user without roles
397+
$email = new Email('no-roles@example.com');
398+
$password = Password::createFromPlainText('password123');
399+
$createdUser = UserIdentity::create(UserId::null(), $email, $password, 'No Initial Roles');
400+
$userRepository = new UserRepository();
401+
$savedUser = $userRepository->create($createdUser);
402+
403+
// Create a role to assign
404+
$role = RoleRecord::create(['name' => 'Guest', 'description' => 'Guest role']);
405+
406+
// Assign role to user that previously had no roles
407+
$user = $userRepository->getById($savedUser->getId());
408+
$roleIds = new RoleIdCollection([RoleId::fromInt($role->id)]);
409+
$user->syncRoles($roleIds);
410+
$userRepository->update($user);
411+
412+
// Verify role was assigned
413+
$userRoles = \DB::table('pivot_user_role')
414+
->where('user_id', $user->getId()->getValue())
415+
->count();
416+
expect($userRoles)->toBe(1);
417+
418+
$roleId = \DB::table('pivot_user_role')
419+
->where('user_id', $user->getId()->getValue())
420+
->value('role_id');
421+
expect($roleId)->toBe($role->id);
422+
});

0 commit comments

Comments
 (0)