> */ public static function listAll(PDO $pdo): array { return $pdo->query(" SELECT u.userID, u.username, u.role, u.entityID, u.mustChangePassword, u.createdAt, COALESCE(a.location, s.location, '') AS location, c.supervisorID, sv.username AS supervisorUsername FROM users u LEFT JOIN admin a ON a.adminID = u.entityID AND u.role = 'admin' LEFT JOIN supervisor s ON s.supervisorID = u.entityID AND u.role = 'supervisor' LEFT JOIN coach c ON c.coachID = u.entityID AND u.role = 'coach' LEFT JOIN supervisor sv ON sv.supervisorID = c.supervisorID ORDER BY u.role, u.username ")->fetchAll(PDO::FETCH_ASSOC); } /** * Coaches that belong to the given supervisor. * @return list> */ public static function listBySupervisor(PDO $pdo, string $supervisorID): array { $svName = self::getSupervisorName($pdo, $supervisorID); $stmt = $pdo->prepare(" SELECT u.userID, u.username, u.role, u.entityID, u.mustChangePassword, u.createdAt, '' AS location, c.supervisorID, :svname AS supervisorUsername FROM users u JOIN coach c ON c.coachID = u.entityID AND u.role = 'coach' WHERE c.supervisorID = :svid ORDER BY u.username "); $stmt->execute([':svid' => $supervisorID, ':svname' => $svName]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } /** * @return list */ public static function listSupervisors(PDO $pdo): array { return $pdo->query( "SELECT supervisorID, username, location FROM supervisor ORDER BY username" )->fetchAll(PDO::FETCH_ASSOC); } /** * @return array|null */ public static function findByUsername(PDO $pdo, string $username): ?array { $stmt = $pdo->prepare("SELECT * FROM users WHERE username = :u"); $stmt->execute([':u' => $username]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row ?: null; } /** * @return array{role: string, entityID: string}|null */ public static function findById(PDO $pdo, string $userID): ?array { $stmt = $pdo->prepare("SELECT role, entityID FROM users WHERE userID = :uid"); $stmt->execute([':uid' => $userID]); $row = $stmt->fetch(PDO::FETCH_ASSOC); return $row ?: null; } public static function usernameExists(PDO $pdo, string $username): bool { $stmt = $pdo->prepare("SELECT 1 FROM users WHERE username = :u"); $stmt->execute([':u' => $username]); return (bool) $stmt->fetch(); } public static function supervisorExists(PDO $pdo, string $supervisorID): bool { $stmt = $pdo->prepare("SELECT 1 FROM supervisor WHERE supervisorID = :sid"); $stmt->execute([':sid' => $supervisorID]); return (bool) $stmt->fetch(); } public static function coachBelongsToSupervisor(PDO $pdo, string $coachEntityID, string $supervisorID): bool { $stmt = $pdo->prepare( "SELECT coachID FROM coach WHERE coachID = :cid AND supervisorID = :svid" ); $stmt->execute([':cid' => $coachEntityID, ':svid' => $supervisorID]); return (bool) $stmt->fetch(); } /** * Insert a new user inside a transaction: role-specific table first, then users. * Caller is responsible for wrapping in qdb_open(true) / qdb_save(). */ public static function create( PDO $pdo, string $userID, string $entityID, string $username, string $passwordHash, string $role, string $location, string $supervisorID, int $mustChangePassword ): void { $pdo->beginTransaction(); try { switch ($role) { case 'admin': $pdo->prepare("INSERT INTO admin (adminID, username, location) VALUES (:id, :u, :loc)") ->execute([':id' => $entityID, ':u' => $username, ':loc' => $location]); break; case 'supervisor': $pdo->prepare("INSERT INTO supervisor (supervisorID, username, location) VALUES (:id, :u, :loc)") ->execute([':id' => $entityID, ':u' => $username, ':loc' => $location]); break; case 'coach': $pdo->prepare("INSERT INTO coach (coachID, supervisorID, username) VALUES (:id, :sid, :u)") ->execute([':id' => $entityID, ':sid' => $supervisorID, ':u' => $username]); break; } $pdo->prepare(" INSERT INTO users (userID, username, passwordHash, role, entityID, mustChangePassword, createdAt) VALUES (:uid, :u, :hash, :role, :eid, :mcp, :now) ")->execute([ ':uid' => $userID, ':u' => $username, ':hash' => $passwordHash, ':role' => $role, ':eid' => $entityID, ':mcp' => $mustChangePassword, ':now' => time(), ]); $pdo->commit(); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } /** * Delete user + role-specific row in a transaction. */ public static function delete(PDO $pdo, string $userID, string $role, string $entityID): void { $pdo->beginTransaction(); try { $pdo->prepare("DELETE FROM users WHERE userID = :uid") ->execute([':uid' => $userID]); switch ($role) { case 'admin': $pdo->prepare("DELETE FROM admin WHERE adminID = :id") ->execute([':id' => $entityID]); break; case 'supervisor': $pdo->prepare("DELETE FROM supervisor WHERE supervisorID = :id") ->execute([':id' => $entityID]); break; case 'coach': $pdo->prepare("DELETE FROM coach WHERE coachID = :id") ->execute([':id' => $entityID]); break; } $pdo->commit(); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } throw $e; } } public static function updatePassword(PDO $pdo, string $userID, string $newHash): void { $pdo->prepare( "UPDATE users SET passwordHash = :h, mustChangePassword = 0 WHERE userID = :uid" )->execute([':h' => $newHash, ':uid' => $userID]); } /** * @return string Username or empty string if not found. */ public static function getSupervisorName(PDO $pdo, string $supervisorID): string { $stmt = $pdo->prepare("SELECT username FROM supervisor WHERE supervisorID = :svid"); $stmt->execute([':svid' => $supervisorID]); return $stmt->fetchColumn() ?: ''; } }