false, 'error' => 'Unauthorized - brak autoryzacji' ], JSON_UNESCAPED_UNICODE); exit; } // Sprawdzenie czy użytkownik ma rolę admina if (!isset($_SESSION['role']) || $_SESSION['role'] !== 'admin') { http_response_code(403); echo json_encode([ 'success' => false, 'error' => 'Forbidden - tylko admini mają dostęp' ], JSON_UNESCAPED_UNICODE); exit; } // Funkcja do zwracania błędów jako JSON function returnError($message, $code = 500) { http_response_code($code); echo json_encode([ 'success' => false, 'error' => $message ], JSON_UNESCAPED_UNICODE); exit; } try { $pdo = og_session_get_pdo(); if (!$pdo instanceof PDO) { throw new PDOException('Nie udało się zainicjalizować połączenia z bazą danych.'); } // Parametry z requestu $page = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $limit = isset($_GET['limit']) ? min(100, max(1, (int)$_GET['limit'])) : 50; $offset = ($page - 1) * $limit; // Sortowanie $sortBy = isset($_GET['sortBy']) ? $_GET['sortBy'] : 'StartTime'; $sortOrder = isset($_GET['sortOrder']) && strtoupper($_GET['sortOrder']) === 'DESC' ? 'DESC' : 'ASC'; // Dozwolone kolumny do sortowania (bezpieczeństwo) $allowedSortColumns = ['ID', 'Team1_ID', 'Team2_ID', 'StartTime', 'Status', 'Score', 'Platform', 'MatchType', 'created_at', 'updated_at']; if (!in_array($sortBy, $allowedSortColumns)) { $sortBy = 'StartTime'; } // Filtrowanie $filters = []; $params = []; // Filtr po statusie meczu if (isset($_GET['status']) && $_GET['status'] !== '') { $filters[] = "Status = :status"; $params[':status'] = $_GET['status']; } // Filtr po platformie if (isset($_GET['platform']) && $_GET['platform'] !== '') { $filters[] = "Platform = :platform"; $params[':platform'] = $_GET['platform']; } // Filtr po typie meczu if (isset($_GET['matchType']) && $_GET['matchType'] !== '') { $filters[] = "MatchType = :matchType"; $params[':matchType'] = $_GET['matchType']; } // Filtr po dacie rozpoczęcia (od) if (isset($_GET['startTime_from']) && $_GET['startTime_from'] !== '') { $filters[] = "StartTime >= :startTime_from"; $params[':startTime_from'] = $_GET['startTime_from']; } // Filtr po dacie rozpoczęcia (do) if (isset($_GET['startTime_to']) && $_GET['startTime_to'] !== '') { $filters[] = "StartTime <= :startTime_to"; $params[':startTime_to'] = $_GET['startTime_to']; } // Filtr po ID drużyny 1 if (isset($_GET['team1_id']) && $_GET['team1_id'] !== '') { $filters[] = "Team1_ID = :team1_id"; $params[':team1_id'] = (int)$_GET['team1_id']; } // Filtr po ID drużyny 2 if (isset($_GET['team2_id']) && $_GET['team2_id'] !== '') { $filters[] = "Team2_ID = :team2_id"; $params[':team2_id'] = (int)$_GET['team2_id']; } // Budowanie WHERE clause $whereClause = ''; if (count($filters) > 0) { $whereClause = 'WHERE ' . implode(' AND ', $filters); } // OPTYMALIZACJA: Fast approximate count z limitem 100k // Sprawdzenie czy count jest w cache (ważny 5 minut) $cacheKey = 'matches_count_' . md5(serialize($params)); $totalRecords = 0; $isApproximate = false; if (isset($_SESSION[$cacheKey]) && isset($_SESSION[$cacheKey . '_time']) && (time() - $_SESSION[$cacheKey . '_time']) < 300) { // Cache hit - użyj zapisanej wartości $totalRecords = $_SESSION[$cacheKey]; $isApproximate = $_SESSION[$cacheKey . '_approx'] ?? false; } else { // Cache miss - policz z limitem // OPTYMALIZACJA: Limit count do 100k dla wydajności $countSql = "SELECT COUNT(*) as total FROM ( SELECT 1 FROM matches $whereClause LIMIT 100000 ) as limited_count"; $countStmt = $pdo->prepare($countSql); try { $countStmt->execute($params); $totalRecords = $countStmt->fetch(PDO::FETCH_ASSOC)['total']; // Jeśli osiągnięto limit, sprawdź czy jest więcej if ($totalRecords >= 100000) { $checkMoreSql = "SELECT EXISTS( SELECT 1 FROM matches $whereClause LIMIT 100001 ) as has_more"; $checkStmt = $pdo->prepare($checkMoreSql); $checkStmt->execute($params); if ($checkStmt->fetch(PDO::FETCH_ASSOC)['has_more']) { $isApproximate = true; $totalRecords = 100000; // Pokazuj 100k+ } } // Zapisz w cache na 5 minut $_SESSION[$cacheKey] = $totalRecords; $_SESSION[$cacheKey . '_time'] = time(); $_SESSION[$cacheKey . '_approx'] = $isApproximate; } catch (PDOException $e) { returnError('Błąd podczas zliczania rekordów: ' . $e->getMessage()); } } $totalPages = $totalRecords > 0 ? ceil($totalRecords / $limit) : 1; // Pobieranie meczów $sql = "SELECT ID, Team1_ID, Team2_ID, StartTime, EndTime, Status, Score, Platform, MatchType, Rate, Participants, created_at, updated_at FROM matches $whereClause ORDER BY $sortBy $sortOrder LIMIT :limit OFFSET :offset"; $stmt = $pdo->prepare($sql); // Bindowanie parametrów filtrów foreach ($params as $key => $value) { $stmt->bindValue($key, $value); } // Bindowanie limit i offset $stmt->bindValue(':limit', $limit, PDO::PARAM_INT); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); try { $stmt->execute(); $matches = $stmt->fetchAll(PDO::FETCH_ASSOC); } catch (PDOException $e) { returnError('Błąd podczas pobierania meczów: ' . $e->getMessage()); } // Formatowanie odpowiedzi $response = [ 'success' => true, 'data' => $matches, 'pagination' => [ 'currentPage' => $page, 'totalPages' => $totalPages, 'totalRecords' => (int)$totalRecords, 'totalRecordsApproximate' => $isApproximate, 'totalRecordsDisplay' => $isApproximate ? '100,000+' : number_format($totalRecords, 0, ',', ' '), 'recordsPerPage' => $limit, 'hasNextPage' => $page < $totalPages, 'hasPreviousPage' => $page > 1 ], 'filters' => [ 'sortBy' => $sortBy, 'sortOrder' => $sortOrder, 'appliedFilters' => array_keys($params) ] ]; echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); } catch (PDOException $e) { returnError('Błąd połączenia z bazą danych: ' . $e->getMessage()); } catch (Exception $e) { returnError('Nieoczekiwany błąd: ' . $e->getMessage()); } ?>