appIds)) { return; } DB::transaction(function () use ($data): void { $role = Role::query()->findOrFail($data->roleId); $this->assignAppPermissionsRole($role, $data->appIds); $records = array_map(fn (int $appId) => [ 'role_id' => $data->roleId, 'app_id' => $appId, 'duration' => $data->validUpto, ], $data->appIds); AppRole::query()->upsert( $records, ['role_id', 'app_id'], ['duration'] ); }); } /** * @param array $appIds Apps, of which all permissions will be assigned to the role. */ private function assignAppPermissionsRole(Role $role, array $appIds): void { array_map(function ($appId) use ($role): void { $app = $this->connectedAppService->getConnectedApp($appId); $permissions = $this->appsPermissionService->getAllPermissions($app); $permissions ->toCollection() ->map(fn ($permission) => $role->givePermissionTo($permission->id)); }, $appIds); } /** * Detach an app from a role; * * @param array $appIds Apps to be detached. */ public function detachApps(array $appIds): void { AppRole::query() ->whereIn('app_id', $appIds) ->delete(); } /** * Remove an app-role assignment. * * @param int $id The ID of the pivot table record. * * @throws InvalidArgumentException when id is invalid. * @throws Throwable when an error occurs during the deletion. */ public function detach(int $id): void { if ($id <= 0) { throw new InvalidArgumentException('Invalid id'); } DB::transaction(function () use ($id): void { $appRole = AppRole::query()->findOrFail($id); $role = Role::query()->findOrFail($appRole->role_id); $this->removeAppPermissionsRole($role, $appRole->app_id); $appRole->delete(); }); } /** * @param array|int $appIds */ private function removeAppPermissionsRole(Role $role, ...$appIds): void { array_map(function ($appId) use ($role): void { $app = $this->connectedAppService->getConnectedApp($appId); $permissions = $this->appsPermissionService->getAllPermissions($app); $permissions ->toCollection() ->map(fn ($permission) => $role->revokePermissionTo($permission->name)); }, $appIds); } /** * Get all apps assigned to a given role. * * @return DataCollection */ #[NoDiscard] public function getAppsForRole(int $roleId): DataCollection { $assignments = AppRole::query() ->with('app:id,name') ->where('role_id', $roleId) ->orderBy('id') ->get(['id', 'app_id', 'role_id', 'duration']); return RoleAppData::collect( $assignments->map(fn (AppRole $row): array => [ 'id' => $row->id, 'appId' => $row->app_id, 'appName' => $row->app->name ?? '', 'duration' => $row->duration, ])->all(), DataCollection::class, ); } /** * Search available apps for the select dropdown. */ #[NoDiscard] public function searchAppsForSelect(string $search = ''): Collection { $query = ConnectedApp::query() ->select(['id', 'name']); if (auth()->check()) { $userPriority = auth()->user()?->roles()->min('priority') ?? 999999; $query->whereHas('roles', function ($q) use ($userPriority): void { $q->where('priority', '>', $userPriority); }); } return $query->when($search, function ($q, $search): void { $q->where('name', 'like', '%'.$search.'%'); }) ->limit(50) // Limit the results so the dropdown doesn't lag ->orderBy('name') ->get(['id', 'name']); } /** * Get all connected app IDs for the "Select All" feature. * * @return array */ #[NoDiscard] public function getAllAppIds(): array { $query = ConnectedApp::query(); if (auth()->check()) { $userPriority = auth()->user()?->roles()->min('priority') ?? 999999; $query->whereHas('roles', function ($q) use ($userPriority): void { $q->where('priority', '>', $userPriority); }); } return $query->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 { $query = User::query() ->select(['id', 'name']); if (auth()->check()) { $userPriority = auth()->user()?->roles()->min('priority') ?? 999999; $query->whereDoesntHave('roles', function ($q) use ($userPriority): void { $q->where('priority', '<=', $userPriority); }); } $users = $query->when($search, function ($q, $search): void { $q->where('name', 'like', '%'.$search.'%'); }) ->limit(50) ->orderBy('name') ->get(); return UserSelectData::collect($users, DataCollection::class); } }