user = model(User::class); $this->userRole = model(UserRole::class); $this->role = model(Role::class); $this->passwordReset = model(PasswordReset::class); $this->resetRequest = model(PasswordResetRequest::class); $this->loginActivity = model(LoginActivity::class); $this->ipAttempt = model(IpAttempt::class); $this->emailService = app(EmailService::class); } public function index(): JsonResponse { $sort = (string) $this->laravelRequest->query('sort', 'id'); $order = strtolower((string) $this->laravelRequest->query('order', 'asc')) === 'desc' ? 'desc' : 'asc'; $allowedFields = ['id', 'firstname', 'lastname', 'email', 'role']; $builder = DB::table('users') ->select( 'users.id', 'users.firstname', 'users.lastname', 'users.email', 'user_roles.role_id', 'roles.name as role', 'users.status', 'users.updated_at' ) ->leftJoin('user_roles', 'user_roles.user_id', '=', 'users.id') ->leftJoin('roles', 'roles.id', '=', 'user_roles.role_id'); if (in_array($sort, $allowedFields, true)) { $column = $sort === 'role' ? 'roles.name' : 'users.' . $sort; $builder->orderBy($column, $order); } else { $builder->orderBy('users.id', 'asc'); } $users = $builder->get()->map(fn($row) => (array) $row)->toArray(); $roles = $this->role->newQuery()->orderBy('name')->get()->toArray(); return $this->respondSuccess([ 'users' => $users, 'roles' => $roles, ], 'Users retrieved successfully'); } public function listWithRoles(): JsonResponse { return $this->respondSuccess([ 'users' => $this->buildUsersWithRoles(), ], 'Users with roles retrieved'); } public function store(): JsonResponse { $data = $this->payloadData(); $rules = [ 'firstname' => 'required|min:2|max:100', 'lastname' => 'required|min:2|max:100', 'password' => 'required|min:8', 'cellphone' => 'required|min:7|max:50', 'email' => 'required|email|unique:users,email', 'address_street' => 'required|min:3|max:150', 'city' => 'required|min:2|max:100', 'state' => 'required|min:2|max:10', 'zip' => 'required|min:3|max:20', 'accept_school_policy' => 'required', 'role_id' => 'required|integer|exists:roles,id', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $userData = [ 'lastname' => ucfirst(strtolower((string) $data['lastname'])), 'firstname' => ucfirst(strtolower((string) $data['firstname'])), 'cellphone' => trim((string) $data['cellphone']), 'email' => strtolower(trim((string) $data['email'])), 'address_street' => trim((string) $data['address_street']), 'city' => ucfirst(strtolower((string) $data['city'])), 'state' => strtoupper(trim((string) $data['state'])), 'zip' => trim((string) $data['zip']), 'accept_school_policy' => in_array($data['accept_school_policy'], [true, '1', 1, 'on'], true) ? 1 : 0, 'password' => $this->hashPassword((string) $data['password']), 'status' => 'Inactive', ]; DB::beginTransaction(); try { $user = $this->user->newQuery()->create($userData); $this->userRole->newQuery()->create([ 'user_id' => $user->id, 'role_id' => (int) $data['role_id'], 'created_at' => utc_now(), ]); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); log_message('error', 'Failed to create user: ' . $e->getMessage()); return $this->respondError('Failed to create user', Response::HTTP_INTERNAL_SERVER_ERROR); } $created = $this->user->find($user->id); unset($created['password'], $created['token']); return $this->success(['user' => $created], 'User created successfully', Response::HTTP_CREATED); } public function destroy(int $id): JsonResponse { $user = $this->user->find($id); if (!$user) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } DB::beginTransaction(); try { $this->userRole->newQuery()->where('user_id', $id)->delete(); $this->user->delete($id); DB::commit(); } catch (\Throwable $e) { DB::rollBack(); log_message('error', 'Failed to delete user: ' . $e->getMessage()); return $this->respondError('Failed to delete user', Response::HTTP_INTERNAL_SERVER_ERROR); } return $this->respondSuccess(null, 'User deleted successfully'); } public function updateUser(int $id): JsonResponse { $payload = $this->payloadData(); $user = $this->user->find($id); if (!$user) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $validationData = array_merge($user, $payload); $rules = [ 'firstname' => 'required|min:2|max:100', 'lastname' => 'required|min:2|max:100', 'email' => 'required|email|unique:users,email,' . $id, 'state' => 'nullable|max:10', 'zip' => 'nullable|max:20', 'cellphone' => 'nullable|max:50', 'failed_attempts' => 'nullable|integer', 'gender' => 'nullable|in:male,female,MALE,FEMALE', ]; $errors = $this->validateRequest($validationData, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $bool = static function ($value, $default = 0) { if ($value === null) { return $default; } return in_array($value, [true, 1, '1', 'true', 'on'], true) ? 1 : 0; }; $parseDateTime = static function ($value) { if ($value === null || $value === '') { return null; } $timestamp = strtotime((string) $value); return $timestamp ? date('Y-m-d H:i:s', $timestamp) : null; }; $data = [ 'id' => $id, 'account_id' => trim((string) ($payload['account_id'] ?? $user['account_id'] ?? '')) ?: null, 'lastname' => trim((string) ($payload['lastname'] ?? $user['lastname'] ?? '')), 'firstname' => trim((string) ($payload['firstname'] ?? $user['firstname'] ?? '')), 'gender' => trim((string) ($payload['gender'] ?? $user['gender'] ?? '')) ?: null, 'cellphone' => trim((string) ($payload['cellphone'] ?? $user['cellphone'] ?? '')) ?: null, 'email' => strtolower(trim((string) ($payload['email'] ?? $user['email'] ?? ''))), 'address_street' => trim((string) ($payload['address_street'] ?? $user['address_street'] ?? '')) ?: null, 'apt' => trim((string) ($payload['apt'] ?? $user['apt'] ?? '')) ?: null, 'city' => trim((string) ($payload['city'] ?? $user['city'] ?? '')) ?: null, 'state' => strtoupper(trim((string) ($payload['state'] ?? $user['state'] ?? ''))) ?: null, 'zip' => trim((string) ($payload['zip'] ?? $user['zip'] ?? '')) ?: null, 'accept_school_policy' => $bool($payload['accept_school_policy'] ?? $user['accept_school_policy'] ?? 0, (int) ($user['accept_school_policy'] ?? 0)), 'user_type' => trim((string) ($payload['user_type'] ?? $user['user_type'] ?? '')) ?: null, 'rfid_tag' => trim((string) ($payload['rfid_tag'] ?? $user['rfid_tag'] ?? '')) ?: null, 'school_id' => trim((string) ($payload['school_id'] ?? $user['school_id'] ?? '')) ?: null, 'failed_attempts' => ($payload['failed_attempts'] ?? '') === '' ? null : (int) $payload['failed_attempts'], 'last_failed_at' => $parseDateTime($payload['last_failed_at'] ?? $user['last_failed_at'] ?? null), 'semester' => trim((string) ($payload['semester'] ?? $user['semester'] ?? '')) ?: null, 'school_year' => trim((string) ($payload['school_year'] ?? $user['school_year'] ?? '')) ?: null, 'status' => trim((string) ($payload['status'] ?? $user['status'] ?? '')) ?: null, 'is_suspended' => $bool($payload['is_suspended'] ?? $user['is_suspended'] ?? 0, (int) ($user['is_suspended'] ?? 0)), 'is_verified' => $bool($payload['is_verified'] ?? $user['is_verified'] ?? 0, (int) ($user['is_verified'] ?? 0)), 'token' => trim((string) ($payload['token'] ?? $user['token'] ?? '')) ?: null, 'updated_at' => $parseDateTime($payload['updated_at'] ?? null) ?? $user['updated_at'], 'created_at' => $parseDateTime($payload['created_at'] ?? null) ?? $user['created_at'], ]; if (!$this->user->save($data)) { return $this->respondError('Failed to update user', Response::HTTP_INTERNAL_SERVER_ERROR); } $updated = $this->user->find($id); unset($updated['password'], $updated['token']); return $this->respondSuccess($updated, 'User updated successfully'); } public function profile(): JsonResponse { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $userData = $this->user->find($user->id); if (!$userData) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $roles = $this->fetchUserRoles($user->id); unset($userData['password'], $userData['token']); $userData['roles'] = $roles; return $this->success($userData, 'Profile retrieved successfully'); } public function profileAuth(): JsonResponse { $data = $this->payloadData(); if (empty($data['email']) || empty($data['password'])) { return $this->respondError('Email and password are required', Response::HTTP_BAD_REQUEST); } $user = $this->user->where('email', $data['email'])->first(); if (!$user || !$this->verifyPassword($data['password'], $user['password'] ?? '')) { return $this->respondError('Invalid credentials', Response::HTTP_UNAUTHORIZED); } $roles = $this->fetchUserRoles($user['id']); $tokenPayload = $this->buildLoginPayload($user, $roles); Session::put([ 'user_id' => (int) $user['id'], 'roles' => $roles, ]); return $this->success($tokenPayload, 'Authenticated successfully'); } public function updateProfile(): JsonResponse { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $data = $this->payloadData(); if (empty($data)) { return $this->respondError('Invalid request data', Response::HTTP_BAD_REQUEST); } $allowedFields = [ 'firstname', 'lastname', 'cellphone', 'address_street', 'apt', 'city', 'state', 'zip', 'gender', ]; $updateData = []; foreach ($allowedFields as $field) { if (array_key_exists($field, $data)) { $updateData[$field] = $data[$field]; } } $userRecord = $this->user->find($user->id); if (!$userRecord) { return $this->respondError('User not found', Response::HTTP_NOT_FOUND); } $emailChanging = isset($data['email']) && $data['email'] !== $userRecord['email']; $passwordChanging = isset($data['password']) && trim((string) $data['password']) !== ''; if ($emailChanging) { if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) { return $this->respondError('Invalid email format', Response::HTTP_UNPROCESSABLE_ENTITY); } $existing = $this->user->newQuery() ->where('email', $data['email']) ->where('id', '!=', $user->id) ->first(); if ($existing) { return $this->respondError('Email already in use', Response::HTTP_UNPROCESSABLE_ENTITY); } } if ($emailChanging || $passwordChanging) { $current = $data['current_password'] ?? ''; if ($current === '' || !$this->verifyPassword($current, $userRecord['password'] ?? '')) { return $this->respondError('Current password is required to change email or password', Response::HTTP_UNAUTHORIZED); } } if ($emailChanging) { $updateData['email'] = $data['email']; } if ($passwordChanging) { $updateData['password'] = $this->hashPassword($data['password']); } if (empty($updateData)) { return $this->respondError('No valid fields to update', Response::HTTP_BAD_REQUEST); } try { $this->user->where('id', $user->id)->update($updateData); $updated = $this->user->find($user->id); unset($updated['password'], $updated['token']); return $this->success($updated, 'Profile updated successfully'); } catch (\Throwable $e) { log_message('error', 'Profile update error: ' . $e->getMessage()); return $this->respondError('Failed to update profile', Response::HTTP_INTERNAL_SERVER_ERROR); } } public function requestPasswordReset(): JsonResponse { $data = $this->payloadData(); $email = strtolower(trim((string) ($data['email'] ?? ''))); if ($email === '') { return $this->respondValidationError(['email' => ['Email is required.']]); } $ip = $this->laravelRequest->ip(); $now = CarbonImmutable::now(); $recentAttempts = $this->resetRequest->newQuery() ->where('ip_address', $ip) ->where('requested_at', '>=', $now->subDay()->toDateTimeString()) ->count(); if ($recentAttempts >= 3) { log_message('warning', 'Password reset blocked for IP: ' . $ip); return $this->respondError('Too many reset attempts. Try again later.', Response::HTTP_TOO_MANY_REQUESTS); } $this->resetRequest->newQuery()->create([ 'ip_address' => $ip, 'requested_at' => $now->toDateTimeString(), ]); $user = $this->user->where('email', $email)->first(); if (!$user) { return $this->respondSuccess(null, 'If the email is registered, a reset link will be sent.'); } if ((int) ($user['is_verified'] ?? 0) === 0) { return $this->respondError('Please activate your account before resetting the password.', Response::HTTP_BAD_REQUEST); } $token = bin2hex(random_bytes(48)); $expiresAt = $now->addHour(); $this->passwordReset->newQuery()->create([ 'email' => $email, 'token' => $token, 'created_at' => $now->toDateTimeString(), 'expires_at' => $expiresAt->toDateTimeString(), ]); $this->sendResetEmail($email, $token); return $this->respondSuccess(null, 'Password reset link sent if the email exists.'); } public function validateResetToken(string $token): JsonResponse { $now = CarbonImmutable::now()->toDateTimeString(); $entry = $this->passwordReset->newQuery() ->where('token', $token) ->where('expires_at', '>=', $now) ->first(); if (!$entry) { return $this->respondError('Invalid or expired reset token.', Response::HTTP_BAD_REQUEST); } return $this->respondSuccess([ 'email' => $entry['email'], ], 'Token valid'); } public function completePasswordReset(): JsonResponse { $data = $this->payloadData(); $rules = [ 'token' => 'required', 'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@\-=\+*#$%&!?]).{8,}$/', 'password_confirmation' => 'required|same:password', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $now = CarbonImmutable::now()->toDateTimeString(); $resetEntry = $this->passwordReset->newQuery() ->where('token', $data['token']) ->where('expires_at', '>=', $now) ->first(); if (!$resetEntry) { return $this->respondError('Invalid or expired reset link.', Response::HTTP_BAD_REQUEST); } $user = $this->user->where('email', $resetEntry['email'])->first(); if (!$user) { return $this->respondError('User not found.', Response::HTTP_NOT_FOUND); } $hashedPassword = $this->hashPassword($data['password']); $this->user->update($user['id'], [ 'failed_attempts' => 0, 'is_suspended' => 0, 'last_failed_at' => null, 'password' => $hashedPassword, 'status' => 'Active', ]); $this->passwordReset->newQuery()->where('token', $data['token'])->delete(); $ip = $this->laravelRequest->ip(); $attempt = $this->ipAttempt->newQuery()->where('ip_address', $ip)->first(); if ($attempt) { $this->ipAttempt->newQuery()->where('ip_address', $ip)->delete(); } return $this->respondSuccess(null, 'Password has been reset successfully.'); } public function confirm(string $token): JsonResponse { $user = $this->user->where('token', $token)->first(); if (!$user) { return $this->respondError('Invalid or expired confirmation token.', Response::HTTP_BAD_REQUEST); } if ((int) ($user['is_verified'] ?? 0) === 1) { return $this->respondSuccess([ 'user_id' => $user['id'], 'email' => $user['email'], ], 'Account already verified.'); } return $this->respondSuccess([ 'user_id' => $user['id'], 'email' => $user['email'], ], 'Token valid. Please set your password.'); } public function setPassword(): JsonResponse { $data = $this->payloadData(); $rules = [ 'user_id' => 'required|integer|exists:users,id', 'token' => 'required', 'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@\-=\+*#$%&!?]).{8,}$/', 'password_confirm' => 'required|same:password', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $user = $this->user->newQuery() ->where('id', $data['user_id']) ->where('token', $data['token']) ->first(); if (!$user) { return $this->respondError('Invalid token or user.', Response::HTTP_BAD_REQUEST); } $accountId = $user['account_id'] ?? null; if (!$accountId) { $accountId = 'ACC' . str_pad((string) $user['id'], 8, '0', STR_PAD_LEFT); } $this->user->update($user['id'], [ 'password' => $this->hashPassword($data['password']), 'status' => 'Active', 'is_verified' => 1, 'token' => null, 'account_id' => $accountId, ]); return $this->respondSuccess(null, 'Password set successfully. You may now log in.'); } public function loginActivity(): JsonResponse { $perPage = (int) ($this->laravelRequest->query('per_page', 25)); $page = (int) ($this->laravelRequest->query('page', 1)); return $this->respondSuccess( $this->buildLoginActivityPayload($perPage, $page), 'Login activity retrieved' ); } protected function buildUsersWithRoles(): array { $users = $this->user->newQuery()->get()->map(fn($user) => (array) $user)->toArray(); if (empty($users)) { return []; } $guardianMap = []; try { $rows = DB::table('family_guardians') ->select('user_id', DB::raw('MAX(is_primary) as max_primary'), DB::raw('COUNT(*) as cnt')) ->groupBy('user_id') ->get(); foreach ($rows as $row) { $uid = (int) ($row->user_id ?? 0); if ($uid <= 0) { continue; } $guardianMap[$uid] = ((int) ($row->max_primary ?? 0)) > 0 ? 'Primary' : 'Secondary'; } } catch (\Throwable $e) { // legacy table missing; ignore } $secondByUserId = []; $secondByEmail = []; try { $uids = DB::table('parents') ->select('secondparent_user_id as uid') ->where('secondparent_user_id', '!=', 0) ->groupBy('secondparent_user_id') ->get(); foreach ($uids as $row) { $u = (int) ($row->uid ?? 0); if ($u > 0) { $secondByUserId[$u] = true; } } $emails = DB::table('parents') ->select(DB::raw('LOWER(TRIM(secondparent_email)) as email')) ->where('secondparent_email', '!=', '') ->groupBy('secondparent_email') ->get(); foreach ($emails as $row) { $em = trim(strtolower((string) ($row->email ?? ''))); if ($em !== '') { $secondByEmail[$em] = true; } } } catch (\Throwable $e) { // ignore legacy table issues } $result = []; foreach ($users as $user) { if (!is_array($user)) { continue; } $roles = $this->userRole->where('user_id', $user['id'] ?? 0)->findAll() ?? []; $roleNames = []; $roleIds = []; foreach ($roles as $userRole) { if (!is_array($userRole) || empty($userRole['role_id'])) { continue; } $role = $this->role->find($userRole['role_id']); if ($role) { $roleIds[] = (int) $role['id']; $roleNames[] = $role['name']; } } $email = trim(strtolower((string) ($user['email'] ?? ''))); $uid = (int) ($user['id'] ?? 0); $isSecond = ($uid > 0 && !empty($secondByUserId[$uid])) || ($email !== '' && !empty($secondByEmail[$email])); $result[] = [ 'user' => $user, 'roles' => $roleNames, 'role_ids' => $roleIds, 'parent_type' => $guardianMap[$uid] ?? '', 'second_from_parents' => $isSecond ? 'Yes' : '', ]; } return $result; } protected function sendResetEmail(string $email, string $token): void { $resetLink = url('/reset-password?token=' . urlencode($token)); try { $html = view('emails/reset_password', ['resetLink' => $resetLink])->render(); } catch (\Throwable $e) { $html = '
Click the link below to reset your password:
'; } $this->emailService->send($email, 'Password Reset Request', $html, 'general'); } protected function fetchUserRoles(int $userId): array { return $this->userRole->newQuery() ->select('roles.name') ->join('roles', 'roles.id', '=', 'user_roles.role_id') ->where('user_roles.user_id', $userId) ->pluck('name') ->toArray(); } protected function buildLoginPayload(array $user, array $roles): array { $now = time(); $exp = $now + 60 * 60 * 24; $payload = [ 'sub' => (int) $user['id'], 'name' => trim(($user['firstname'] ?? '') . ' ' . ($user['lastname'] ?? '')), 'roles' => $roles, 'iat' => $now, 'exp' => $exp, ]; $token = $this->encodeJwt($payload); $rolesMap = []; foreach ($roles as $role) { $rolesMap[$role] = true; } return [ 'token' => $token, 'user' => [ 'id' => (int) $user['id'], 'name' => $payload['name'], 'email' => $user['email'], 'roles' => (object) $rolesMap, ], ]; } protected function encodeJwt(array $payload): string { $header = ['alg' => 'HS256', 'typ' => 'JWT']; $secret = env('JWT_SECRET', 'change-me-in-env'); $segments = [ $this->base64UrlEncode(json_encode($header)), $this->base64UrlEncode(json_encode($payload)), ]; $signature = hash_hmac('sha256', implode('.', $segments), $secret, true); $segments[] = $this->base64UrlEncode($signature); return implode('.', $segments); } protected function base64UrlEncode(string $data): string { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } protected function verifyPassword(string $password, string $storedHash): bool { $delimiter = str_contains($storedHash, '$') ? '$' : ':'; $parts = explode($delimiter, $storedHash); if (count($parts) !== 4) { return false; } [$algo, $iterations, $salt, $hash] = $parts; $calc = hash_pbkdf2($algo, $password, $salt, (int) $iterations, strlen($hash)); return hash_equals($hash, $calc); } protected function hashPassword(string $password): string { $algo = 'sha256'; $iterations = 200000; $salt = bin2hex(random_bytes(16)); $hash = hash_pbkdf2($algo, $password, $salt, $iterations, 64); return implode(':', [$algo, $iterations, $salt, $hash]); } protected function buildLoginActivityPayload(int $perPage, int $page): array { $perPage = max(1, min($perPage, 200)); $page = max(1, $page); $query = $this->loginActivity->newQuery()->orderBy('login_time', 'DESC'); $total = (int) $query->count(); $activities = $query->forPage($page, $perPage)->get()->map(fn($row) => (array) $row)->toArray(); $normalized = array_map(static function ($activity) { return [ 'user_id' => $activity['user_id'] ?? null, 'email' => $activity['email'] ?? null, 'login_time' => $activity['login_time'] ?? null, 'logout_time' => $activity['logout_time'] ?? null, 'ip_address' => $activity['ip_address'] ?? null, 'user_agent' => $activity['user_agent'] ?? null, ]; }, $activities); $pageCount = (int) max(1, ceil($total / $perPage)); return [ 'activities' => $normalized, 'pagination' => [ 'total' => $total, 'perPage' => $perPage, 'currentPage' => $page, 'pageCount' => $pageCount, 'hasNext' => $page < $pageCount, 'hasPrevious' => $page > 1, ], ]; } /** * POST /api/v1/users/select-role * Select and update user's role in database */ public function selectRole(): JsonResponse { $user = $this->getCurrentUser(); if (!$user) { return $this->respondError('Authentication required', Response::HTTP_UNAUTHORIZED); } $data = $this->payloadData(); $roleKey = (string) ($data['role'] ?? ''); if ($roleKey === '') { return $this->respondError('Role is required', Response::HTTP_BAD_REQUEST); } log_message('info', 'Processing setRole form submission for user: ' . $user->id); log_message('info', 'Role selected: ' . $roleKey); $route = $this->role->getRouteByNameOrSlug($roleKey); if ($route === null) { log_message('error', 'Invalid or inactive role selected: ' . $roleKey); return $this->respondError('Invalid role selected.', Response::HTTP_BAD_REQUEST); } try { // Update the user's role field in the database $this->user->update($user->id, ['role' => $roleKey]); log_message('info', 'Role updated in database for user: ' . $user->id); log_message('info', 'Redirecting to role dashboard: ' . $route); return $this->success([ 'role' => $roleKey, 'route' => $route, ], 'Role selected successfully'); } catch (\Throwable $e) { log_message('error', 'Role selection error: ' . $e->getMessage()); return $this->respondError('Failed to select role', Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * DELETE /api/v1/users/roles/{roleId} * Delete a role and reassign users to Guest role */ public function deleteRole(int $roleId): JsonResponse { $role = $this->role->find($roleId); if (!$role) { return $this->respondError('Role not found', Response::HTTP_NOT_FOUND); } // Fetch the "Guest" role $guestRole = $this->role->where('name', 'Guest')->first(); if (!$guestRole) { return $this->respondError('Guest role not found', Response::HTTP_NOT_FOUND); } DB::beginTransaction(); try { // Update users with the deleted role to have the "Guest" role $this->user->newQuery() ->where('role_id', $roleId) ->update([ 'role_id' => $guestRole['id'], 'role' => 'Guest', 'status' => 'Inactive', ]); // Also update user_roles table $this->userRole->newQuery() ->where('role_id', $roleId) ->update(['role_id' => $guestRole['id']]); // Delete the role $this->role->delete($roleId); DB::commit(); log_message('info', 'Role deleted: ' . $roleId . ', users reassigned to Guest role'); return $this->respondSuccess(null, 'Role deleted successfully. Users with this role have been assigned the Guest role.'); } catch (\Throwable $e) { DB::rollBack(); log_message('error', 'Failed to delete role: ' . $e->getMessage()); return $this->respondError('Failed to delete role', Response::HTTP_INTERNAL_SERVER_ERROR); } } }