mirror of
git://f0xx.org/ac/ac-ms-issues
synced 2026-07-29 04:18:42 +03:00
initial
This commit is contained in:
77
public/api/diag.php
Normal file
77
public/api/diag.php
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
if (!cfg('debug')) {
|
||||
json_out(['ok' => false, 'error' => 'diag disabled'], 403);
|
||||
}
|
||||
|
||||
$driver = Database::driver();
|
||||
$path = cfg('db.sqlite_path');
|
||||
$out = [
|
||||
'ok' => true,
|
||||
'db_driver' => $driver,
|
||||
'php_user' => get_current_user(),
|
||||
'storage_writable' => is_writable(dirname(__DIR__) . '/../storage'),
|
||||
'tables' => [],
|
||||
'reports_columns' => [],
|
||||
'test_insert' => null,
|
||||
];
|
||||
|
||||
if ($driver === 'sqlite') {
|
||||
$out['sqlite_path'] = $path;
|
||||
$out['sqlite_realpath'] = is_string($path) ? realpath($path) : null;
|
||||
$out['sqlite_exists'] = is_string($path) && is_file($path);
|
||||
$out['sqlite_writable'] = is_string($path) && is_writable($path);
|
||||
$out['data_dir_writable'] = is_string($path) && is_writable(dirname($path));
|
||||
} else {
|
||||
$m = cfg('db.mysql', []);
|
||||
$socket = trim((string) ($m['socket'] ?? ''));
|
||||
$out['mysql'] = [
|
||||
'host' => $m['host'] ?? '127.0.0.1',
|
||||
'port' => (int) ($m['port'] ?? 3306),
|
||||
'socket' => $socket,
|
||||
'socket_exists' => $socket !== '' && file_exists($socket),
|
||||
'database' => $m['database'] ?? 'androidcast_crashes',
|
||||
'username' => $m['username'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = Database::pdo();
|
||||
$tables = Database::listTables($pdo);
|
||||
$out['tables'] = $tables;
|
||||
$out['tickets_table'] = Database::ticketsTableExists($pdo);
|
||||
if (Database::isMysql()) {
|
||||
try {
|
||||
$out['mysql_database'] = $pdo->query('SELECT DATABASE()')->fetchColumn();
|
||||
} catch (Throwable $e) {
|
||||
$out['mysql_database'] = $e->getMessage();
|
||||
}
|
||||
}
|
||||
if (!$out['tickets_table']) {
|
||||
$out['tickets_migration'] = Database::ticketsMigrationHint();
|
||||
}
|
||||
if (in_array('reports', $tables, true)) {
|
||||
$cols = Database::columnNames($pdo, 'reports');
|
||||
$out['reports_columns'] = array_map(
|
||||
static fn (string $name) => ['name' => $name],
|
||||
$cols
|
||||
);
|
||||
}
|
||||
$pdo->beginTransaction();
|
||||
$stmt = $pdo->prepare(
|
||||
'INSERT INTO reports (report_id, fingerprint, crash_type, generated_at_ms, received_at_ms,
|
||||
device_model, app_version, payload_json, tags_json)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)'
|
||||
);
|
||||
$rid = 'diag-' . time();
|
||||
$stmt->execute([$rid, 'diag', 'java', 1, 1, null, null, '{}', '[]']);
|
||||
$pdo->rollBack();
|
||||
$out['test_insert'] = 'ok (rolled back)';
|
||||
} catch (Throwable $e) {
|
||||
$out['ok'] = false;
|
||||
$out['test_insert'] = $e->getMessage();
|
||||
}
|
||||
|
||||
json_out($out);
|
||||
114
public/api/live_cast.php
Normal file
114
public/api/live_cast.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
$action = '';
|
||||
$body = [];
|
||||
|
||||
if ($method === 'GET') {
|
||||
$action = trim((string) ($_GET['action'] ?? 'active'));
|
||||
} else {
|
||||
$raw = file_get_contents('php://input') ?: '';
|
||||
$body = json_decode($raw, true);
|
||||
if (!is_array($body)) {
|
||||
json_out(['ok' => false, 'error' => 'invalid_json'], 400);
|
||||
}
|
||||
$action = trim((string) ($body['action'] ?? ''));
|
||||
}
|
||||
|
||||
$public = ($method === 'GET' && $action === 'session')
|
||||
|| ($method === 'POST' && $action === 'join_event');
|
||||
|
||||
if ($method === 'POST' && in_array($action, ['create_intent', 'heartbeat'], true)) {
|
||||
$platform = strtolower(trim((string) ($body['platform'] ?? '')));
|
||||
if ($action === 'create_intent' && str_starts_with($platform, 'education')) {
|
||||
$public = true;
|
||||
}
|
||||
if ($action === 'heartbeat') {
|
||||
$sid = trim((string) ($body['session_id'] ?? ''));
|
||||
if ($sid !== '') {
|
||||
$existing = LiveCastRepository::sessionById($sid);
|
||||
$existingPlatform = strtolower((string) ($existing['platform'] ?? ''));
|
||||
if ($existing !== null && str_starts_with($existingPlatform, 'education')) {
|
||||
$public = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$public) {
|
||||
Auth::check();
|
||||
}
|
||||
$actor = Auth::user() ?? [];
|
||||
|
||||
if ($method === 'GET') {
|
||||
if ($action === 'active') {
|
||||
$platform = trim((string) ($_GET['platform'] ?? ''));
|
||||
$rows = LiveCastRepository::activeSessions($actor, $platform !== '' ? $platform : null);
|
||||
json_out(['ok' => true, 'sessions' => $rows]);
|
||||
}
|
||||
if ($action === 'analytics') {
|
||||
$days = (int) ($_GET['days'] ?? 14);
|
||||
$payload = LiveCastRepository::analytics($days, $actor);
|
||||
json_out(['ok' => true, 'data' => $payload]);
|
||||
}
|
||||
if ($action === 'list') {
|
||||
$days = (int) ($_GET['days'] ?? 14);
|
||||
$limit = (int) ($_GET['limit'] ?? 200);
|
||||
$rows = LiveCastRepository::listSessions($days, $actor, $limit);
|
||||
json_out(['ok' => true, 'sessions' => $rows]);
|
||||
}
|
||||
if ($action === 'session') {
|
||||
$sessionId = trim((string) ($_GET['session_id'] ?? ''));
|
||||
if ($sessionId === '') {
|
||||
json_out(['ok' => false, 'error' => 'missing_session_id'], 400);
|
||||
}
|
||||
$row = LiveCastRepository::publicSession($sessionId);
|
||||
if ($row === null) {
|
||||
json_out(['ok' => false, 'error' => 'not_found'], 404);
|
||||
}
|
||||
json_out(['ok' => true, 'session' => $row]);
|
||||
}
|
||||
json_out(['ok' => false, 'error' => 'unknown_action'], 400);
|
||||
}
|
||||
|
||||
if ($method !== 'POST') {
|
||||
json_out(['ok' => false, 'error' => 'method_not_allowed'], 405);
|
||||
}
|
||||
|
||||
if ($action === 'create_intent') {
|
||||
$row = LiveCastRepository::createIntent($body, $actor);
|
||||
if ($row === []) {
|
||||
json_out(['ok' => false, 'error' => 'create_failed'], 500);
|
||||
}
|
||||
json_out(['ok' => true, 'session' => $row]);
|
||||
}
|
||||
|
||||
if ($action === 'heartbeat') {
|
||||
$sessionId = trim((string) ($body['session_id'] ?? ''));
|
||||
if ($sessionId === '') {
|
||||
json_out(['ok' => false, 'error' => 'missing_session_id'], 400);
|
||||
}
|
||||
$row = LiveCastRepository::heartbeat($sessionId, $body, $actor);
|
||||
if ($row === null) {
|
||||
json_out(['ok' => false, 'error' => 'not_found'], 404);
|
||||
}
|
||||
json_out(['ok' => true, 'session' => $row]);
|
||||
}
|
||||
|
||||
if ($action === 'join_event') {
|
||||
$sessionId = trim((string) ($body['session_id'] ?? ''));
|
||||
if ($sessionId === '') {
|
||||
json_out(['ok' => false, 'error' => 'missing_session_id'], 400);
|
||||
}
|
||||
$ok = LiveCastRepository::registerJoin($sessionId, $body, $actor);
|
||||
if (!$ok) {
|
||||
json_out(['ok' => false, 'error' => 'not_found'], 404);
|
||||
}
|
||||
json_out(['ok' => true]);
|
||||
}
|
||||
|
||||
json_out(['ok' => false, 'error' => 'unknown_action'], 400);
|
||||
84
public/api/live_cast_signaling.php
Normal file
84
public/api/live_cast_signaling.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
$sessionId = trim((string) ($_GET['session_id'] ?? ''));
|
||||
$sinceId = (int) ($_GET['since_id'] ?? 0);
|
||||
$msgType = trim((string) ($_GET['msg_type'] ?? ''));
|
||||
$fromRole = trim((string) ($_GET['from_role'] ?? ''));
|
||||
$body = [];
|
||||
|
||||
if ($method === 'POST') {
|
||||
$raw = file_get_contents('php://input') ?: '';
|
||||
$body = json_decode($raw, true);
|
||||
if (!is_array($body)) {
|
||||
json_out(['ok' => false, 'error' => 'invalid_json'], 400);
|
||||
}
|
||||
$sessionId = trim((string) ($body['session_id'] ?? $sessionId));
|
||||
}
|
||||
|
||||
if ($sessionId === '') {
|
||||
json_out(['ok' => false, 'error' => 'missing_session_id'], 400);
|
||||
}
|
||||
|
||||
$session = LiveCastRepository::sessionById($sessionId);
|
||||
if ($session === null) {
|
||||
json_out(['ok' => false, 'error' => 'not_found'], 404);
|
||||
}
|
||||
|
||||
$platform = strtolower((string) ($session['platform'] ?? ''));
|
||||
$status = strtolower((string) ($session['status'] ?? ''));
|
||||
$isPublic = str_starts_with($platform, 'education')
|
||||
|| in_array($status, ['intent', 'live'], true);
|
||||
|
||||
if (!$isPublic) {
|
||||
Auth::check();
|
||||
}
|
||||
|
||||
if ($method === 'GET') {
|
||||
$action = trim((string) ($_GET['action'] ?? 'poll'));
|
||||
if ($action === 'latest') {
|
||||
$latest = LiveCastSignalingRepository::latest(
|
||||
$sessionId,
|
||||
$msgType !== '' ? $msgType : 'offer',
|
||||
$fromRole !== '' ? $fromRole : null
|
||||
);
|
||||
json_out(['ok' => true, 'message' => $latest]);
|
||||
}
|
||||
$messages = LiveCastSignalingRepository::poll(
|
||||
$sessionId,
|
||||
$sinceId,
|
||||
$msgType !== '' ? $msgType : null,
|
||||
$fromRole !== '' ? $fromRole : null
|
||||
);
|
||||
$lastId = $sinceId;
|
||||
foreach ($messages as $m) {
|
||||
$lastId = max($lastId, (int) ($m['id'] ?? 0));
|
||||
}
|
||||
json_out(['ok' => true, 'messages' => $messages, 'since_id' => $lastId]);
|
||||
}
|
||||
|
||||
if ($method !== 'POST') {
|
||||
json_out(['ok' => false, 'error' => 'method_not_allowed'], 405);
|
||||
}
|
||||
|
||||
$action = trim((string) ($body['action'] ?? 'post'));
|
||||
if ($action !== 'post') {
|
||||
json_out(['ok' => false, 'error' => 'unknown_action'], 400);
|
||||
}
|
||||
|
||||
$peerRole = trim((string) ($body['peer_role'] ?? 'caster'));
|
||||
$type = trim((string) ($body['msg_type'] ?? ''));
|
||||
$payload = $body['payload'] ?? null;
|
||||
if (!is_array($payload)) {
|
||||
$payload = ['sdp' => (string) ($body['sdp'] ?? ''), 'type' => (string) ($body['type'] ?? '')];
|
||||
}
|
||||
if ($type === '') {
|
||||
json_out(['ok' => false, 'error' => 'missing_msg_type'], 400);
|
||||
}
|
||||
|
||||
$id = LiveCastSignalingRepository::post($sessionId, $peerRole, $type, $payload);
|
||||
json_out(['ok' => true, 'id' => $id]);
|
||||
73
public/api/report_tags.php
Normal file
73
public/api/report_tags.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
if (!Auth::user()) {
|
||||
json_out(['ok' => false, 'error' => 'unauthorized'], 401);
|
||||
}
|
||||
|
||||
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||||
$id = (int) ($_GET['id'] ?? 0);
|
||||
|
||||
if ($method === 'GET') {
|
||||
if ($id <= 0) {
|
||||
json_out(['ok' => false, 'error' => 'invalid id'], 400);
|
||||
}
|
||||
$report = ReportRepository::getById($id);
|
||||
if (!$report) {
|
||||
json_out(['ok' => false, 'error' => 'not found'], 404);
|
||||
}
|
||||
json_out([
|
||||
'ok' => true,
|
||||
'id' => $id,
|
||||
'tags' => $report['tags'] ?? [],
|
||||
'workflow' => TagCatalog::workflowPresets(),
|
||||
'presets' => TagCatalog::editorPresets(),
|
||||
'can_edit' => Auth::canEditTags((int) ($report['company_id'] ?? 0)),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($method !== 'PUT' && $method !== 'POST') {
|
||||
json_out(['ok' => false, 'error' => 'method not allowed'], 405);
|
||||
}
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = is_string($raw) && $raw !== '' ? json_decode($raw, true) : $_POST;
|
||||
if (!is_array($data)) {
|
||||
json_out(['ok' => false, 'error' => 'invalid json'], 400);
|
||||
}
|
||||
$id = (int) ($data['id'] ?? $id);
|
||||
if ($id <= 0) {
|
||||
json_out(['ok' => false, 'error' => 'invalid id'], 400);
|
||||
}
|
||||
|
||||
$tagsIn = $data['tags'] ?? [];
|
||||
if (!is_array($tagsIn)) {
|
||||
json_out(['ok' => false, 'error' => 'tags must be array'], 400);
|
||||
}
|
||||
|
||||
$report = ReportRepository::getById($id);
|
||||
if (!$report) {
|
||||
json_out(['ok' => false, 'error' => 'not found'], 404);
|
||||
}
|
||||
|
||||
if (!Auth::canEditTags((int) ($report['company_id'] ?? 0))) {
|
||||
json_out(['ok' => false, 'error' => 'forbidden'], 403);
|
||||
}
|
||||
|
||||
try {
|
||||
ReportRepository::setCustomTags($id, $tagsIn);
|
||||
$updated = ReportRepository::getById($id);
|
||||
json_out([
|
||||
'ok' => true,
|
||||
'id' => $id,
|
||||
'tags' => $updated['tags'] ?? [],
|
||||
]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('report_tags: ' . $e->getMessage());
|
||||
$out = ['ok' => false, 'error' => 'save failed'];
|
||||
if (cfg('debug')) {
|
||||
$out['hint'] = $e->getMessage();
|
||||
}
|
||||
json_out($out, 500);
|
||||
}
|
||||
25
public/api/report_viewed.php
Normal file
25
public/api/report_viewed.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
if (!Auth::user()) {
|
||||
json_out(['ok' => false, 'error' => 'unauthorized'], 401);
|
||||
}
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
json_out(['ok' => false, 'error' => 'POST required'], 405);
|
||||
}
|
||||
|
||||
$raw = file_get_contents('php://input');
|
||||
$data = is_string($raw) && $raw !== '' ? json_decode($raw, true) : $_POST;
|
||||
$id = (int) ($data['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
json_out(['ok' => false, 'error' => 'invalid id'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
ReportRepository::markViewed($id, (int) Auth::user()['id']);
|
||||
json_out(['ok' => true, 'id' => $id]);
|
||||
} catch (Throwable $e) {
|
||||
error_log('report_viewed: ' . $e->getMessage());
|
||||
json_out(['ok' => false, 'error' => 'failed'], 500);
|
||||
}
|
||||
87
public/api/reports.php
Normal file
87
public/api/reports.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
if (!Auth::user()) {
|
||||
json_out(['ok' => 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);
|
||||
}
|
||||
18
public/api/sfu_health.php
Normal file
18
public/api/sfu_health.php
Normal file
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$sfu = cfg('sfu', []);
|
||||
$enabled = (bool) ($sfu['enabled'] ?? false);
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'sfu' => [
|
||||
'enabled' => $enabled,
|
||||
'status' => $enabled ? 'preview' : 'disabled',
|
||||
'signaling_url' => $enabled ? (string) ($sfu['signaling_url'] ?? '') : '',
|
||||
'janus_url' => $enabled ? (string) ($sfu['janus_url'] ?? '') : '',
|
||||
],
|
||||
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
15
public/api/tag_catalog.php
Normal file
15
public/api/tag_catalog.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
if (!Auth::user()) {
|
||||
json_out(['ok' => false, 'error' => 'unauthorized'], 401);
|
||||
}
|
||||
|
||||
json_out([
|
||||
'ok' => true,
|
||||
'workflow' => TagCatalog::workflowPresets(),
|
||||
'presets' => TagCatalog::editorPresets(),
|
||||
'filter_mode_default' => 'and',
|
||||
'filter_mode_help' => 'AND = report has all listed tags; OR = any listed tag',
|
||||
]);
|
||||
53
public/api/upload.php
Normal file
53
public/api/upload.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/*
|
||||
* package examples/crash_reporter/backend/public/api/upload.php
|
||||
* upload.php
|
||||
* Created at: Wed 20 May 2026 14:31:55 +0200
|
||||
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
|
||||
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
|
||||
* Contributors:
|
||||
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 25 lines)
|
||||
* - Cursor Agent (project assistant)
|
||||
* Digest: SHA256 edbab2563cb9d5ba1c9a6a6272071ac12efa782274c7d1626f088a951033d5a6
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/../../src/bootstrap.php';
|
||||
|
||||
$raw = read_crash_upload_body();
|
||||
if ($raw === null) {
|
||||
json_out(['ok' => false, 'error' => 'empty body'], 400);
|
||||
}
|
||||
if ($raw === false) {
|
||||
json_out(['ok' => false, 'error' => 'invalid gzip body'], 400);
|
||||
}
|
||||
$data = json_decode($raw, true);
|
||||
if (!is_array($data)) {
|
||||
json_out(['ok' => false, 'error' => 'invalid json'], 400);
|
||||
}
|
||||
if ((int) ($data['schema_version'] ?? 0) !== 1) {
|
||||
json_out(['ok' => false, 'error' => 'unsupported schema_version'], 400);
|
||||
}
|
||||
try {
|
||||
ReportRepository::insert($data);
|
||||
json_out(['ok' => true, 'report_id' => $data['report_id'] ?? null]);
|
||||
} catch (PDOException $e) {
|
||||
$msg = $e->getMessage();
|
||||
if ($e->getCode() === '23000' || stripos($msg, 'UNIQUE') !== false) {
|
||||
json_out(['ok' => false, 'error' => 'duplicate report_id'], 409);
|
||||
}
|
||||
error_log('crash upload PDO: ' . $msg);
|
||||
log_crash_event('upload', 'PDO: ' . $msg);
|
||||
$out = ['ok' => false, 'error' => 'store failed'];
|
||||
if (cfg('debug')) {
|
||||
$out['hint'] = $msg;
|
||||
}
|
||||
json_out($out, 500);
|
||||
} catch (Throwable $e) {
|
||||
error_log('crash upload: ' . $e->getMessage());
|
||||
log_crash_event('upload', $e->getMessage());
|
||||
$out = ['ok' => false, 'error' => 'store failed'];
|
||||
if (cfg('debug')) {
|
||||
$out['hint'] = $e->getMessage();
|
||||
}
|
||||
json_out($out, 500);
|
||||
}
|
||||
Reference in New Issue
Block a user