feat(phase1): complete base structure - landing page, auth, dashboard, airports, API, DB schema, 60 airports CSV
This commit is contained in:
142
www/public/api/airports.php
Normal file
142
www/public/api/airports.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
/**
|
||||
* Airline Tycoon - Airports API
|
||||
* Flughafen-bezogene API-Endpunkte
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'list':
|
||||
handleList();
|
||||
break;
|
||||
|
||||
case 'get':
|
||||
handleGet();
|
||||
break;
|
||||
|
||||
case 'search':
|
||||
handleSearch();
|
||||
break;
|
||||
|
||||
case 'distance':
|
||||
handleDistance();
|
||||
break;
|
||||
|
||||
default:
|
||||
jsonResponse(['error' => 'Unbekannte Aktion'], 400);
|
||||
}
|
||||
|
||||
function handleList() {
|
||||
global $db;
|
||||
|
||||
// Airports aus CSV laden falls nötig
|
||||
$airports = $db->getAllAirports();
|
||||
if (empty($airports)) {
|
||||
$config = require dirname(__DIR__) . '/src/config.php';
|
||||
$count = $db->loadAirportsFromCsv($config['paths']['data'] . '/airports.csv');
|
||||
$airports = $db->getAllAirports();
|
||||
}
|
||||
|
||||
// Optional filtern
|
||||
$continent = $_GET['continent'] ?? null;
|
||||
$country = $_GET['country'] ?? null;
|
||||
|
||||
if ($continent) {
|
||||
$airports = array_filter($airports, fn($a) => $a['continent'] === $continent);
|
||||
}
|
||||
|
||||
if ($country) {
|
||||
$airports = array_filter($airports, fn($a) => $a['country'] === $country);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'airports' => array_values($airports),
|
||||
'count' => count($airports)
|
||||
]);
|
||||
}
|
||||
|
||||
function handleGet() {
|
||||
global $db;
|
||||
|
||||
$id = (int) ($_GET['id'] ?? 0);
|
||||
$iata = $_GET['iata'] ?? '';
|
||||
|
||||
if ($id > 0) {
|
||||
$airport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$id]);
|
||||
} elseif ($iata) {
|
||||
$airport = $db->getAirportByIata($iata);
|
||||
} else {
|
||||
jsonResponse(['error' => 'ID oder IATA-Code erforderlich'], 400);
|
||||
}
|
||||
|
||||
if (!$airport) {
|
||||
jsonResponse(['error' => 'Flughafen nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'airport' => $airport
|
||||
]);
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
global $db;
|
||||
|
||||
$query = trim($_GET['q'] ?? '');
|
||||
|
||||
if (strlen($query) < 2) {
|
||||
jsonResponse(['error' => 'Suchbegriff zu kurz (min 2 Zeichen)'], 400);
|
||||
}
|
||||
|
||||
$query = '%' . $query . '%';
|
||||
|
||||
$airports = $db->select(
|
||||
"SELECT * FROM airports
|
||||
WHERE name LIKE ? OR city LIKE ? OR iata_code LIKE ? OR country LIKE ?
|
||||
ORDER BY name
|
||||
LIMIT 20",
|
||||
[$query, $query, $query, $query]
|
||||
);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'airports' => $airports,
|
||||
'count' => count($airports)
|
||||
]);
|
||||
}
|
||||
|
||||
function handleDistance() {
|
||||
global $db;
|
||||
|
||||
$from = (int) ($_GET['from'] ?? 0);
|
||||
$to = (int) ($_GET['to'] ?? 0);
|
||||
|
||||
if ($from <= 0 || $to <= 0) {
|
||||
jsonResponse(['error' => 'Gültige Flughafen-IDs erforderlich'], 400);
|
||||
}
|
||||
|
||||
$fromAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$from]);
|
||||
$toAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$to]);
|
||||
|
||||
if (!$fromAirport || !$toAirport) {
|
||||
jsonResponse(['error' => 'Ein oder beide Flughäfen nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
$distance = $db->calculateDistance(
|
||||
$fromAirport['latitude'], $fromAirport['longitude'],
|
||||
$toAirport['latitude'], $toAirport['longitude']
|
||||
);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'distance' => $distance,
|
||||
'from' => $fromAirport,
|
||||
'to' => $toAirport
|
||||
]);
|
||||
}
|
||||
189
www/public/api/auth.php
Normal file
189
www/public/api/auth.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* Airline Tycoon - Auth API
|
||||
* Authentifizierungs-bezogene API-Endpunkte
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Nur JSON-Antworten
|
||||
if (!isAjax()) {
|
||||
jsonResponse(['error' => 'Nur AJAX-Anfragen erlaubt'], 403);
|
||||
}
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
|
||||
switch ($action) {
|
||||
case 'login':
|
||||
handleLogin();
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
handleRegister();
|
||||
break;
|
||||
|
||||
case 'logout':
|
||||
handleLogout();
|
||||
break;
|
||||
|
||||
case 'me':
|
||||
handleMe();
|
||||
break;
|
||||
|
||||
case 'check':
|
||||
handleCheck();
|
||||
break;
|
||||
|
||||
default:
|
||||
jsonResponse(['error' => 'Unbekannte Aktion'], 400);
|
||||
}
|
||||
|
||||
function handleLogin() {
|
||||
global $db;
|
||||
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
if (empty($username) || empty($password)) {
|
||||
jsonResponse(['error' => 'Bitte fülle alle Felder aus'], 400);
|
||||
}
|
||||
|
||||
$user = $db->getUserByUsername($username);
|
||||
|
||||
if ($user && password_verify($password, $user['password_hash'])) {
|
||||
Session::login($user);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Anmeldung erfolgreich!',
|
||||
'redirect' => BASE_PATH . 'dashboard.php',
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'airline_name' => $user['airline_name'],
|
||||
'balance' => $user['balance'],
|
||||
'level' => $user['level']
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
jsonResponse(['error' => 'Benutzername oder Passwort falsch'], 401);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRegister() {
|
||||
global $db, $config;
|
||||
|
||||
$username = trim($_POST['username'] ?? '');
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$password = $_POST['password'] ?? '';
|
||||
$airline_name = trim($_POST['airline_name'] ?? '');
|
||||
|
||||
// Validation
|
||||
if (empty($username) || empty($email) || empty($password)) {
|
||||
jsonResponse(['error' => 'Bitte fülle alle Pflichtfelder aus'], 400);
|
||||
}
|
||||
|
||||
if (strlen($username) < 3 || strlen($username) > 50) {
|
||||
jsonResponse(['error' => 'Benutzername muss zwischen 3 und 50 Zeichen haben'], 400);
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
|
||||
jsonResponse(['error' => 'Benutzername darf nur Buchstaben, Zahlen und Unterstriche enthalten'], 400);
|
||||
}
|
||||
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
jsonResponse(['error' => 'Ungültige E-Mail-Adresse'], 400);
|
||||
}
|
||||
|
||||
if (strlen($password) < 8) {
|
||||
jsonResponse(['error' => 'Passwort muss mindestens 8 Zeichen haben'], 400);
|
||||
}
|
||||
|
||||
// Check existing
|
||||
if ($db->getUserByUsername($username)) {
|
||||
jsonResponse(['error' => 'Benutzername bereits vergeben'], 409);
|
||||
}
|
||||
|
||||
if ($db->getUserByEmail($email)) {
|
||||
jsonResponse(['error' => 'E-Mail-Adresse wird bereits verwendet'], 409);
|
||||
}
|
||||
|
||||
// Create user
|
||||
$passwordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||
$userId = $db->createUser($username, $email, $passwordHash);
|
||||
|
||||
// Set airline name
|
||||
$finalAirlineName = !empty($airline_name) ? $airline_name : $username . "'s Airlines";
|
||||
$db->update('users', ['airline_name' => $finalAirlineName], 'id = ?', [$userId]);
|
||||
|
||||
// Load airports if needed
|
||||
$airportCount = $db->select("SELECT COUNT(*) as cnt FROM airports")[0]['cnt'] ?? 0;
|
||||
if ($airportCount == 0) {
|
||||
$db->loadAirportsFromCsv($config['paths']['data'] . '/airports.csv');
|
||||
}
|
||||
|
||||
// Auto login
|
||||
$user = $db->getUserById($userId);
|
||||
Session::login($user);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Registrierung erfolgreich! Willkommen bei ' . $finalAirlineName . '!',
|
||||
'redirect' => BASE_PATH . 'dashboard.php',
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'airline_name' => $user['airline_name'],
|
||||
'balance' => $user['balance'],
|
||||
'level' => $user['level']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
Session::logout();
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Abmeldung erfolgreich',
|
||||
'redirect' => BASE_PATH . 'login.php'
|
||||
]);
|
||||
}
|
||||
|
||||
function handleMe() {
|
||||
if (!Session::isLoggedIn()) {
|
||||
jsonResponse(['error' => 'Nicht angemeldet'], 401);
|
||||
}
|
||||
|
||||
global $db;
|
||||
$userId = Session::getUserId();
|
||||
$user = $db->getUserById($userId);
|
||||
|
||||
if (!$user) {
|
||||
Session::logout();
|
||||
jsonResponse(['error' => 'Benutzer nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'email' => $user['email'],
|
||||
'airline_name' => $user['airline_name'],
|
||||
'balance' => $user['balance'],
|
||||
'level' => $user['level'],
|
||||
'experience' => $user['experience']
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
function handleCheck() {
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'authenticated' => Session::isLoggedIn(),
|
||||
'csrf_token' => Session::getCsrfToken()
|
||||
]);
|
||||
}
|
||||
331
www/public/api/game.php
Normal file
331
www/public/api/game.php
Normal file
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* Airline Tycoon - Game API
|
||||
* Hauptspiel-API für Flüge, Routen, Flugzeuge, etc.
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
// Auth Check für alle Aktionen außer 'status'
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||
|
||||
$publicActions = ['status', 'info'];
|
||||
if (!in_array($action, $publicActions) && !Session::isLoggedIn()) {
|
||||
jsonResponse(['error' => 'Nicht angemeldet'], 401);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'status':
|
||||
handleStatus();
|
||||
break;
|
||||
|
||||
case 'info':
|
||||
handleInfo();
|
||||
break;
|
||||
|
||||
case 'purchase':
|
||||
handlePurchase();
|
||||
break;
|
||||
|
||||
case 'flight':
|
||||
handleFlight();
|
||||
break;
|
||||
|
||||
case 'routes':
|
||||
handleRoutes();
|
||||
break;
|
||||
|
||||
case 'create_route':
|
||||
handleCreateRoute();
|
||||
break;
|
||||
|
||||
case 'stats':
|
||||
handleStats();
|
||||
break;
|
||||
|
||||
case 'leaderboard':
|
||||
handleLeaderboard();
|
||||
break;
|
||||
|
||||
default:
|
||||
jsonResponse(['error' => 'Unbekannte Aktion'], 400);
|
||||
}
|
||||
|
||||
function handleStatus() {
|
||||
global $db, $config;
|
||||
|
||||
$response = [
|
||||
'success' => true,
|
||||
'authenticated' => Session::isLoggedIn(),
|
||||
'timestamp' => time(),
|
||||
'config' => [
|
||||
'start_balance' => $config['game']['start_balance'],
|
||||
'fuel_cost_per_km' => $config['game']['fuel_cost_per_km']
|
||||
]
|
||||
];
|
||||
|
||||
if (Session::isLoggedIn()) {
|
||||
$userId = Session::getUserId();
|
||||
$user = $db->getUserById($userId);
|
||||
|
||||
if ($user) {
|
||||
$response['user'] = [
|
||||
'id' => $user['id'],
|
||||
'username' => $user['username'],
|
||||
'airline_name' => $user['airline_name'],
|
||||
'balance' => $user['balance'],
|
||||
'level' => $user['level'],
|
||||
'experience' => $user['experience']
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
jsonResponse($response);
|
||||
}
|
||||
|
||||
function handleInfo() {
|
||||
global $config;
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'app' => $config['app'],
|
||||
'game' => $config['game'],
|
||||
'aircraft_types' => $config['aircraft_types']
|
||||
]);
|
||||
}
|
||||
|
||||
function handlePurchase() {
|
||||
global $db, $config;
|
||||
|
||||
$userId = Session::getUserId();
|
||||
$user = $db->getUserById($userId);
|
||||
|
||||
$type = $_POST['aircraft_type'] ?? '';
|
||||
$name = trim($_POST['name'] ?? '');
|
||||
|
||||
if (empty($type) || !isset($config['aircraft_types'][$type])) {
|
||||
jsonResponse(['error' => 'Ungültiger Flugzeugtyp'], 400);
|
||||
}
|
||||
|
||||
$specs = $config['aircraft_types'][$type];
|
||||
$price = $specs['purchase_price'];
|
||||
|
||||
if ($user['balance'] < $price) {
|
||||
jsonResponse([
|
||||
'error' => 'Nicht genug Geld!',
|
||||
'required' => $price,
|
||||
'available' => $user['balance']
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Balance abziehen
|
||||
$newBalance = $user['balance'] - $price;
|
||||
$db->update('users', ['balance' => $newBalance], 'id = ?', [$userId]);
|
||||
|
||||
// Aircraft erstellen
|
||||
$aircraftId = $db->purchaseAircraft($userId, $type, $name, $price);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => "{$type} \"{$name}\" wurde gekauft!",
|
||||
'aircraft_id' => $aircraftId,
|
||||
'price' => $price,
|
||||
'new_balance' => $newBalance
|
||||
]);
|
||||
}
|
||||
|
||||
function handleFlight() {
|
||||
global $db;
|
||||
|
||||
$userId = Session::getUserId();
|
||||
$user = $db->getUserById($userId);
|
||||
|
||||
$routeId = (int) ($_POST['route_id'] ?? 0);
|
||||
$aircraftId = (int) ($_POST['aircraft_id'] ?? 0);
|
||||
|
||||
if ($routeId <= 0 || $aircraftId <= 0) {
|
||||
jsonResponse(['error' => 'Route und Flugzeug erforderlich'], 400);
|
||||
}
|
||||
|
||||
// Route prüfen
|
||||
$route = $db->selectOne(
|
||||
"SELECT r.*, a1.name as from_name, a2.name as to_name
|
||||
FROM routes r
|
||||
JOIN airports a1 ON r.from_airport_id = a1.id
|
||||
JOIN airports a2 ON r.to_airport_id = a2.id
|
||||
WHERE r.id = ? AND r.user_id = ? AND r.is_active = 1",
|
||||
[$routeId, $userId]
|
||||
);
|
||||
|
||||
if (!$route) {
|
||||
jsonResponse(['error' => 'Route nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
// Aircraft prüfen
|
||||
$aircraft = $db->selectOne(
|
||||
"SELECT * FROM aircraft WHERE id = ? AND user_id = ?",
|
||||
[$aircraftId, $userId]
|
||||
);
|
||||
|
||||
if (!$aircraft) {
|
||||
jsonResponse(['error' => 'Flugzeug nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
if ($aircraft['condition'] <= 0) {
|
||||
jsonResponse(['error' => 'Flugzeug ist zu beschädigt!'], 400);
|
||||
}
|
||||
|
||||
if ($aircraft['range_km'] < $route['distance_km']) {
|
||||
jsonResponse([
|
||||
'error' => 'Flugzeug hat nicht genug Reichweite!',
|
||||
'required' => $route['distance_km'],
|
||||
'available' => $aircraft['range_km']
|
||||
], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$result = $db->executeFlight($userId, $routeId, $aircraftId);
|
||||
|
||||
// User-Daten aktualisieren
|
||||
$updatedUser = $db->getUserById($userId);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => 'Flug erfolgreich!',
|
||||
'flight' => [
|
||||
'from' => $route['from_name'],
|
||||
'to' => $route['to_name'],
|
||||
'passengers' => $result['passengers'],
|
||||
'revenue' => $result['revenue'],
|
||||
'fuel_cost' => $result['fuel_cost'],
|
||||
'profit' => $result['profit']
|
||||
],
|
||||
'user' => [
|
||||
'balance' => $updatedUser['balance'],
|
||||
'experience' => $updatedUser['experience'],
|
||||
'level' => $updatedUser['level']
|
||||
]
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
jsonResponse(['error' => 'Flug fehlgeschlagen: ' . $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
function handleRoutes() {
|
||||
global $db;
|
||||
|
||||
$userId = Session::getUserId();
|
||||
$routes = $db->getUserRoutes($userId);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'routes' => $routes,
|
||||
'count' => count($routes)
|
||||
]);
|
||||
}
|
||||
|
||||
function handleCreateRoute() {
|
||||
global $db;
|
||||
|
||||
$userId = Session::getUserId();
|
||||
|
||||
$fromId = (int) ($_POST['from_airport'] ?? 0);
|
||||
$toId = (int) ($_POST['to_airport'] ?? 0);
|
||||
|
||||
if ($fromId <= 0 || $toId <= 0) {
|
||||
jsonResponse(['error' => 'Gültige Flughäfen erforderlich'], 400);
|
||||
}
|
||||
|
||||
if ($fromId === $toId) {
|
||||
jsonResponse(['error' => 'Von und Nach müssen unterschiedlich sein'], 400);
|
||||
}
|
||||
|
||||
// Flughäfen prüfen
|
||||
$fromAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$fromId]);
|
||||
$toAirport = $db->selectOne("SELECT * FROM airports WHERE id = ?", [$toId]);
|
||||
|
||||
if (!$fromAirport || !$toAirport) {
|
||||
jsonResponse(['error' => 'Ein oder beide Flughäfen nicht gefunden'], 404);
|
||||
}
|
||||
|
||||
// Prüfen ob Route bereits existiert
|
||||
$existing = $db->selectOne(
|
||||
"SELECT id FROM routes WHERE user_id = ? AND from_airport_id = ? AND to_airport_id = ?",
|
||||
[$userId, $fromId, $toId]
|
||||
);
|
||||
|
||||
if ($existing) {
|
||||
jsonResponse(['error' => 'Diese Route existiert bereits'], 409);
|
||||
}
|
||||
|
||||
// Distance berechnen
|
||||
$distance = $db->calculateDistance(
|
||||
$fromAirport['latitude'], $fromAirport['longitude'],
|
||||
$toAirport['latitude'], $toAirport['longitude']
|
||||
);
|
||||
|
||||
// Route erstellen
|
||||
$routeId = $db->createRoute($userId, $fromId, $toId, $distance);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'message' => "Route {$fromAirport['iata_code']} → {$toAirport['iata_code']} erstellt!",
|
||||
'route_id' => $routeId,
|
||||
'distance' => $distance
|
||||
]);
|
||||
}
|
||||
|
||||
function handleStats() {
|
||||
global $db;
|
||||
|
||||
$userId = Session::getUserId();
|
||||
$user = $db->getUserById($userId);
|
||||
|
||||
$stats = $db->getUserStats($userId);
|
||||
$todayStats = $db->getTodayStats($userId);
|
||||
$aircraft = $db->getUserAircraft($userId);
|
||||
$routes = $db->getUserRoutes($userId);
|
||||
|
||||
// Level-Berechnung
|
||||
$config = require dirname(__DIR__) . '/src/config.php';
|
||||
$expForNextLevel = $config['game']['level_up_threshold'] * pow($config['game']['level_multiplier'], $user['level'] - 1);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'user' => [
|
||||
'balance' => $user['balance'],
|
||||
'level' => $user['level'],
|
||||
'experience' => $user['experience'],
|
||||
'exp_for_next' => $expForNextLevel
|
||||
],
|
||||
'stats' => $stats,
|
||||
'today' => $todayStats,
|
||||
'fleet_count' => count($aircraft),
|
||||
'routes_count' => count($routes)
|
||||
]);
|
||||
}
|
||||
|
||||
function handleLeaderboard() {
|
||||
global $db;
|
||||
|
||||
$limit = min(50, (int) ($_GET['limit'] ?? 10));
|
||||
|
||||
$leaders = $db->select(
|
||||
"SELECT id, username, airline_name, balance, level, experience,
|
||||
(SELECT COUNT(*) FROM routes WHERE user_id = users.id AND is_active = 1) as routes_count,
|
||||
(SELECT COUNT(*) FROM flights WHERE user_id = users.id) as total_flights
|
||||
FROM users
|
||||
WHERE is_active = 1
|
||||
ORDER BY balance DESC, level DESC, experience DESC
|
||||
LIMIT ?",
|
||||
[$limit]
|
||||
);
|
||||
|
||||
jsonResponse([
|
||||
'success' => true,
|
||||
'leaderboard' => $leaders,
|
||||
'count' => count($leaders)
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user