diff --git a/app/Data/Role/AssignUsersToRoleData.php b/app/Data/Role/AssignUsersToRoleData.php new file mode 100644 index 0000000..89d80b7 --- /dev/null +++ b/app/Data/Role/AssignUsersToRoleData.php @@ -0,0 +1,16 @@ +id, + name: $user->name, + initials: $user->initials() + ); + } +} diff --git a/app/Data/Role/UserSelectData.php b/app/Data/Role/UserSelectData.php new file mode 100644 index 0000000..e7a807a --- /dev/null +++ b/app/Data/Role/UserSelectData.php @@ -0,0 +1,19 @@ + */ use HasFactory, SoftDeletes; + use HasRoles; use Notifiable; use TwoFactorAuthenticatable; diff --git a/app/Services/AppRoleService.php b/app/Services/AppRoleService.php index f488a18..aa547b8 100644 --- a/app/Services/AppRoleService.php +++ b/app/Services/AppRoleService.php @@ -4,8 +4,8 @@ namespace App\Services; -use App\Data\Role\{AssignAppsToRoleData, RoleAppData}; -use App\Models\{AppRole, ConnectedApp}; +use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, RoleUserData, UserSelectData}; +use App\Models\{AppRole, ConnectedApp, Role, User}; use Illuminate\Database\Eloquent\Collection; use Illuminate\Support\Facades\DB; use InvalidArgumentException; @@ -105,7 +105,96 @@ public function searchAppsForSelect(string $search = ''): Collection #[NoDiscard] public function getAllAppIds(): array { - // Replace 'ConnectedApp' with your actual model name return ConnectedApp::pluck('id')->toArray(); } + + /** + * Assign users to a role using spatie/laravel-permission. + * + * @throws Throwable when an error occurs during the assignment. + */ + public function assignUsersToRole(AssignUsersToRoleData $data): void + { + if (empty($data->userIds)) { + return; + } + + DB::transaction(function () use ($data): void { + $role = Role::query()->findOrFail($data->roleId); + $users = User::query()->whereIn('id', $data->userIds)->get(); + + /** @var User $user */ + foreach ($users as $user) { + if (! $user->hasRole($role)) { + $user->assignRole($role); + } + } + }); + } + + /** + * Remove a user from a role. + * + * @throws InvalidArgumentException when userId is invalid. + * @throws Throwable when an error occurs during the removal. + */ + public function detachUserFromRole(int $roleId, int $userId): void + { + if ($userId <= 0) { + throw new InvalidArgumentException('Invalid user id'); + } + + DB::transaction(function () use ($roleId, $userId): void { + $role = Role::query()->findOrFail($roleId); + $user = User::query()->findOrFail($userId); + + $user->removeRole($role); + }); + } + + /** + * Get all users assigned to a given role. + * + * @return DataCollection + */ + #[NoDiscard] + public function getUsersForRole(int $roleId): DataCollection + { + $role = Role::query()->findOrFail($roleId); + + $users = User::query() + ->select(['id', 'name', 'email']) + ->role($role) + ->orderBy('name') + ->get(); + + return RoleUserData::collect( + $users->map(fn (User $user): array => [ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + ])->all(), + DataCollection::class, + ); + } + + /** + * Search users for the select dropdown, returning only id and name. + * + * @return DataCollection + */ + #[NoDiscard] + public function searchUsersForSelect(string $search = ''): DataCollection + { + $users = User::query() + ->select(['id', 'name']) + ->when($search, function ($query, $search): void { + $query->where('name', 'like', '%'.$search.'%'); + }) + ->limit(50) + ->orderBy('name') + ->get(); + + return UserSelectData::collect($users, DataCollection::class); + } } diff --git a/app/Services/RoleService.php b/app/Services/RoleService.php index eb68e3a..cdd2c24 100644 --- a/app/Services/RoleService.php +++ b/app/Services/RoleService.php @@ -55,7 +55,7 @@ public function delete(int $id): void public function getAll(): PaginatedDataCollection { $roles = Role::query() - ->with('apps') + ->with(['apps:id,name', 'users:id,name']) ->orderBy('id') ->paginate(); diff --git a/resources/views/components/dashboard-sidebar.blade.php b/resources/views/components/dashboard-sidebar.blade.php index 628cfd3..0def7e6 100644 --- a/resources/views/components/dashboard-sidebar.blade.php +++ b/resources/views/components/dashboard-sidebar.blade.php @@ -58,8 +58,8 @@ public function navItems(): DataCollection sub_menu: NavSubItemData::collect([ new NavSubItemData( label: 'Roles and Apps', - route: 'access-manager.roles-and-apps', - active_pattern: 'access-manager.roles-and-apps', + route: 'access-manager.roles-and-apps.index', + active_pattern: 'access-manager.roles-and-apps.*', ), ], DataCollection::class) ), diff --git a/resources/views/pages/access-manager/roles-and-apps/⚡index.blade.php b/resources/views/pages/access-manager/roles-and-apps/⚡index.blade.php index 399cbd7..f0ea531 100644 --- a/resources/views/pages/access-manager/roles-and-apps/⚡index.blade.php +++ b/resources/views/pages/access-manager/roles-and-apps/⚡index.blade.php @@ -3,7 +3,7 @@ use App\Concerns\HandlesOperations; use App\Livewire\Forms\CreateRoleForm; use App\Services\AppRoleService; -use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData}; +use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData, RoleUserData}; use App\Data\Ui\TableHeader; use App\Services\RoleService; use Livewire\Attributes\{Computed, Layout, Title, Validate}; @@ -37,7 +37,7 @@ public function mount(): void $this->headers = TableHeader::collect([ new TableHeader(key: 'name', label: 'Name'), new TableHeader(key: 'apps', label: 'Apps'), - new TableHeader(key: '', label: 'Users'), + new TableHeader(key: 'users', label: 'Users'), ], DataCollection::class); } @@ -132,6 +132,17 @@ public function delete(int $roleId): void @endforeach @endscope + @scope('cell_users', $row) +
+ @foreach($row->users as $user) + @php /** @var RoleUserData $user */ @endphp + + @endforeach +
+ @endscope + @scope('actions', $row)
userHeaders = TableHeader::collect([ new TableHeader(key: 'name', label: 'Name'), - new TableHeader(key: 'email', label: 'Email'), ], DataCollection::class); } @@ -86,14 +86,12 @@ public function apps(): DataCollection } /** - * TODO: Replace with a service method that returns user DTOs for this role. - * - * @return array> + * Get users assigned to this role via spatie/laravel-permission. */ #[Computed] - public function getRoleUsers(): array + public function getRoleUsers(): DataCollection { - return []; + return $this->appRoleService->getUsersForRole($this->roleId); } @@ -208,24 +206,51 @@ public function detachApp(int $assignmentId): void public function openAddUserModal(): void { - $this->reset('selectedUserId'); + $this->reset('selectedUserIds'); $this->resetErrorBag(); + $this->searchUsers(''); $this->showAddUserModal = true; } public function searchUsers(string $value = ''): void { - $this->usersSearchable = []; + $this->usersSearchable = $this + ->appRoleService + ->searchUsersForSelect($value) + ->toArray(); } public function assignUser(): void { - $this->success('User assignment not implemented yet.'); + $this->validate([ + 'selectedUserIds' => 'required|array|min:1', + 'selectedUserIds.*' => 'integer|exists:users,id', + ]); + + $this->attempt( + action: function (): void { + $this->appRoleService->assignUsersToRole(new AssignUsersToRoleData( + roleId: $this->roleId, + userIds: $this->selectedUserIds, + )); + + $this->reset('selectedUserIds'); + $this->showAddUserModal = false; + unset($this->getRoleUsers); + }, + successMessage: 'Users assigned successfully!', + ); } public function detachUser(int $userId): void { - $this->success('User removal not implemented yet.'); + $this->attempt( + action: function () use ($userId): void { + $this->appRoleService->detachUserFromRole($this->roleId, $userId); + unset($this->getRoleUsers); + }, + successMessage: 'User removed from role.', + ); } }; ?> @@ -235,7 +260,7 @@ public function detachUser(int $userId): void
- + Add App @@ -292,26 +317,22 @@ class="btn-sm btn-error btn-soft btn-circle" @endif - + Add User - @if (count($this->getRoleUsers)) + @if ($this->getRoleUsers->count()) @scope('cell_name', $row) {{ $row['name'] }} @endscope - @scope('cell_email', $row) - {{ $row['email'] }} - @endscope - @scope('actions', $row) diff --git a/routes/web.php b/routes/web.php index fa7acec..9de2dfe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -10,8 +10,10 @@ Route::livewire('dashboard', 'pages::dashboard')->name('dashboard'); Route::prefix('access-manager')->name('access-manager.')->group(function (): void { - Route::livewire('roles-and-apps', 'pages::access-manager.roles-and-apps.index')->name('roles-and-apps'); - Route::livewire('roles-and-apps/{id}', 'pages::access-manager.roles-and-apps.show')->name('roles-and-apps.show'); + Route::prefix('roles-and-apps')->name('roles-and-apps.')->group(function (): void { + Route::livewire('roles-and-apps', 'pages::access-manager.roles-and-apps.index')->name('index'); + Route::livewire('roles-and-apps/{id}', 'pages::access-manager.roles-and-apps.show')->name('show'); + }); }); Route::prefix('apps')->name('apps.')->group(function (): void {