false, 'error' => 'unauthorized'], 401); } $grouped = isset($_GET['group']) && $_GET['group'] === '1'; $page = max(1, (int) ($_GET['page'] ?? 1)); $perPage = (int) ($_GET['per_page'] ?? 50); $allowedPerPage = [25, 50, 75, 100, 200]; if (!in_array($perPage, $allowedPerPage, true)) { $perPage = 50; } $sort = (string) ($_GET['sort'] ?? ($grouped ? 'cnt' : 'priority')); $dir = (string) ($_GET['dir'] ?? 'desc'); $sinceMs = max(0, (int) ($_GET['since_ms'] ?? 0)); $similarTo = max(0, (int) ($_GET['similar_to'] ?? 0)); $searchQ = trim((string) ($_GET['q'] ?? '')); $suggest = isset($_GET['suggest']) && $_GET['suggest'] === '1'; $tagIds = request_tag_filter_ids(); $tagMode = request_tag_filter_mode(); $filters = []; if ($tagIds !== []) { $filters['tags'] = $tagIds; $filters['tag_mode'] = $tagMode; } $device = trim((string) ($_GET['filter_device'] ?? '')); if ($device !== '') { $filters['device'] = $device; } $abi = trim((string) ($_GET['filter_abi'] ?? '')); if ($abi !== '') { $filters['abi'] = $abi; } $osSdk = max(0, (int) ($_GET['filter_os_sdk'] ?? 0)); if ($osSdk > 0) { $filters['os_sdk'] = $osSdk; } $osRelease = trim((string) ($_GET['filter_os_release'] ?? '')); if ($osRelease !== '') { $filters['os_release'] = $osRelease; } $appVersion = trim((string) ($_GET['filter_app_version'] ?? '')); if ($appVersion !== '') { $filters['app_version'] = $appVersion; } $build = trim((string) ($_GET['filter_build'] ?? '')); if ($build !== '') { $filters['build'] = $build; } try { if ($suggest) { json_out([ 'ok' => true, 'suggestions' => ReportRepository::searchSuggestions($searchQ), 'q' => $searchQ, ]); } if ($searchQ !== '') { $data = ReportRepository::search($searchQ, $page, $perPage, $sinceMs); } elseif ($similarTo > 0) { $data = ReportRepository::listSimilar($similarTo, $page, $perPage, $sinceMs); } elseif ($filters !== []) { if ($tagIds !== []) { $grouped = false; } $data = ReportRepository::listFiltered($filters, $page, $perPage, $sort, $dir, $sinceMs); } else { $data = ReportRepository::listPage($grouped, $page, $perPage, $sort, $dir, $sinceMs); } json_out(['ok' => true, 'tag_filter' => $tagIds, 'tag_mode' => $tagMode] + $data); } catch (Throwable $e) { error_log('reports api: ' . $e->getMessage()); $out = ['ok' => false, 'error' => 'list failed']; if (cfg('debug')) { $out['hint'] = $e->getMessage(); if (str_contains($e->getMessage(), '2002')) { $out['hint'] .= ' — use db.mysql.socket (/run/mysqld/mysqld.sock on Alpine); TCP 3306 is closed'; } } json_out($out, 500); }