false, 'error' => 'DB not found']); exit; } try { $db = new SQLite3($dbPath, SQLITE3_OPEN_READWRITE); } catch (Exception $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => 'DB open failed']); exit; } $action = $_GET['action'] ?? 'domains'; if ($action === 'domains') { $q = trim($_GET['q'] ?? ''); $tld = trim($_GET['tld'] ?? ''); $onlyNew = (($_GET['only_new'] ?? '0') === '1'); $runId = $db->querySingle("SELECT value FROM metadata WHERE key='latest_run_id'"); $scannedAt = $db->querySingle("SELECT value FROM metadata WHERE key='latest_scanned_at'"); $stPrev = $db->prepare("SELECT run_id FROM domains WHERE run_id != :run GROUP BY run_id ORDER BY MAX(scanned_at) DESC LIMIT 1"); $stPrev->bindValue(':run', $runId, SQLITE3_TEXT); $prevRes = $stPrev->execute(); $prev = $prevRes ? $prevRes->fetchArray(SQLITE3_ASSOC) : null; $prevRunId = $prev['run_id'] ?? ''; $sql = "SELECT d.domain, d.tld, d.score, d.status, d.keywords_json, ls.llm_score, ls.decision, ls.reason, CASE WHEN :prev = '' THEN 1 WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0 ELSE 1 END AS is_new FROM domains d LEFT JOIN llm_scores ls ON ls.run_id = d.run_id AND ls.domain = d.domain WHERE d.run_id = :run"; if ($q !== '') $sql .= " AND d.domain LIKE :q"; if ($tld !== '') $sql .= " AND d.tld = :tld"; if ($onlyNew) { $sql .= " AND (CASE WHEN :prev = '' THEN 1 WHEN EXISTS (SELECT 1 FROM domains p WHERE p.run_id = :prev AND p.domain = d.domain) THEN 0 ELSE 1 END) = 1"; } $sql .= " ORDER BY d.score DESC, d.domain ASC LIMIT 500"; $st = $db->prepare($sql); $st->bindValue(':run', $runId, SQLITE3_TEXT); $st->bindValue(':prev', $prevRunId, SQLITE3_TEXT); if ($q !== '') $st->bindValue(':q', '%' . $q . '%', SQLITE3_TEXT); if ($tld !== '') $st->bindValue(':tld', $tld, SQLITE3_TEXT); $res = $st->execute(); $rows = []; $newCount = 0; while ($r = $res->fetchArray(SQLITE3_ASSOC)) { $r['keywords'] = json_decode($r['keywords_json'] ?: '[]', true) ?: []; $r['is_new'] = intval($r['is_new']) === 1; if ($r['is_new']) $newCount++; unset($r['keywords_json']); $rows[] = $r; } echo json_encode([ 'ok' => true, 'meta' => [ 'runId' => $runId, 'prevRunId' => $prevRunId, 'scannedAt' => $scannedAt, 'count' => count($rows), 'newCount' => $newCount ], 'domains' => $rows, ], JSON_UNESCAPED_UNICODE); exit; } if ($action === 'prices') { if ($_SERVER['REQUEST_METHOD'] === 'POST') { $raw = json_decode(file_get_contents('php://input'), true) ?: []; $registrar = trim($raw['registrar'] ?? ''); $tld = trim($raw['tld'] ?? ''); $registerPrice = $raw['register_price'] ?? null; $renewPrice = $raw['renew_price'] ?? null; if ($registrar === '' || $tld === '') { http_response_code(400); echo json_encode(['ok' => false, 'error' => 'registrar/tld required']); exit; } $st = $db->prepare("UPDATE registrar_prices SET register_price=:rp, renew_price=:rr, updated_at=datetime('now') WHERE registrar=:r AND tld=:t"); if ($registerPrice === null || $registerPrice === '') $st->bindValue(':rp', null, SQLITE3_NULL); else $st->bindValue(':rp', floatval($registerPrice), SQLITE3_FLOAT); if ($renewPrice === null || $renewPrice === '') $st->bindValue(':rr', null, SQLITE3_NULL); else $st->bindValue(':rr', floatval($renewPrice), SQLITE3_FLOAT); $st->bindValue(':r', $registrar, SQLITE3_TEXT); $st->bindValue(':t', $tld, SQLITE3_TEXT); $st->execute(); echo json_encode(['ok' => true]); exit; } $res = $db->query("SELECT registrar, url, tld, register_price, renew_price, updated_at FROM registrar_prices ORDER BY registrar, tld"); $items = []; while ($r = $res->fetchArray(SQLITE3_ASSOC)) $items[] = $r; echo json_encode(['ok' => true, 'items' => $items], JSON_UNESCAPED_UNICODE); exit; } http_response_code(400); echo json_encode(['ok' => false, 'error' => 'unknown action']);