query("SELECT value FROM config WHERE key = 'caching_live_enabled'")->fetchColumn(); $newVal = ($current === 'true') ? 'false' : 'true'; $dataType = 'boolean'; $stmt = $pdo->prepare("INSERT INTO config (key, value, data_type) VALUES ('caching_live_enabled', ?, ?) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"); $stmt->execute([$newVal, $dataType]); // Bump config generation to trigger hot-reload $pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT WHERE key = 'caching_config_generation'"); json_response(['ok' => true, 'enabled' => $newVal === 'true']); } catch (PDOException $e) { json_response(['ok' => false, 'error' => $e->getMessage()], 500); } } elseif ($action === 'save_config') { $strategy = $input['strategy'] ?? ''; $kinds = $input['kinds'] ?? ''; $since = $input['since_seconds'] ?? ''; $limit = $input['limit'] ?? ''; try { $pdo->beginTransaction(); if ($strategy === 'whitelist' || $strategy === 'cache_all') { $stmt = $pdo->prepare("INSERT INTO config (key, value, data_type) VALUES ('caching_live_strategy', ?, 'string') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"); $stmt->execute([$strategy]); } if ($kinds !== '') { $stmt = $pdo->prepare("INSERT INTO config (key, value, data_type) VALUES ('caching_live_kinds', ?, 'string') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"); $stmt->execute([$kinds]); } if ($since !== '') { $stmt = $pdo->prepare("INSERT INTO config (key, value, data_type) VALUES ('caching_live_since_seconds', ?, 'integer') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"); $stmt->execute([(string)(int)$since]); } if ($limit !== '') { $stmt = $pdo->prepare("INSERT INTO config (key, value, data_type) VALUES ('caching_live_limit', ?, 'integer') ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT"); $stmt->execute([(string)(int)$limit]); } // Bump config generation to trigger hot-reload $pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT WHERE key = 'caching_config_generation'"); $pdo->commit(); json_response(['ok' => true, 'message' => 'Live subscription config saved.']); } catch (PDOException $e) { $pdo->rollBack(); json_response(['ok' => false, 'error' => $e->getMessage()], 500); } } elseif ($action === 'toggle_relay') { // Toggle live_enabled or backfill_enabled for a relay in caching_relays $relay_url = $input['relay_url'] ?? ''; $column = $input['column'] ?? ''; if ($relay_url === '' || !in_array($column, ['live_enabled', 'backfill_enabled'])) { json_response(['ok' => false, 'error' => 'Invalid relay_url or column'], 400); exit; } try { $stmt = $pdo->prepare("UPDATE caching_relays SET $column = NOT $column, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT WHERE relay_url = ?"); $stmt->execute([$relay_url]); // Bump config generation to trigger hot-reload $pdo->exec("UPDATE config SET value = (COALESCE(value::int, 0) + 1)::text, updated_at = EXTRACT(EPOCH FROM NOW())::BIGINT WHERE key = 'caching_config_generation'"); json_response(['ok' => true, 'message' => 'Relay toggled.']); } catch (PDOException $e) { json_response(['ok' => false, 'error' => $e->getMessage()], 500); } } else { json_response(['ok' => false, 'error' => 'Unknown action'], 400); } exit; } // --- GET: return current live subscription config + status --- $config = []; try { $cfg_rows = $pdo->query("SELECT key, value FROM config WHERE key IN ( 'caching_live_strategy','caching_live_kinds','caching_live_since_seconds', 'caching_live_limit','caching_live_enabled', 'caching_kinds','caching_bootstrap_relays' )")->fetchAll(); foreach ($cfg_rows as $r) { $config[$r['key']] = $r['value']; } } catch (PDOException $e) {} // Live subscription status from caching_service_state $state = []; try { $state = $pdo->query("SELECT * FROM caching_service_state WHERE id = 1")->fetch() ?: []; } catch (PDOException $e) {} // Unified relay list from caching_relays table $relays = []; try { $relays = $pdo->query(" SELECT relay_url, live_enabled, backfill_enabled, status_code, status_text, follow_count, is_bootstrap FROM caching_relays ORDER BY follow_count DESC, relay_url ")->fetchAll(); } catch (PDOException $e) {} // If caching_relays table is empty (migration not yet run), fall back to old sources if (empty($relays)) { // Upstream relay status (old table) $upstreamRelays = []; try { $upstreamRelays = $pdo->query(" SELECT relay_url, status_code, status_text, updated_at FROM caching_upstream_relays ORDER BY relay_url ")->fetchAll(); } catch (PDOException $e) {} // Discovered relays with follow counts (from backfill progress) $discoveredRelays = []; try { $discoveredRelays = $pdo->query(" SELECT relay_url, COUNT(DISTINCT author_pubkey) AS follow_count FROM caching_backfill_relay_progress GROUP BY relay_url ORDER BY follow_count DESC, relay_url ")->fetchAll(); } catch (PDOException $e) {} json_response([ 'config' => $config, 'state' => $state, 'upstreamRelays' => $upstreamRelays, 'discoveredRelays' => $discoveredRelays, 'relays' => [], ]); } else { json_response([ 'config' => $config, 'state' => $state, 'relays' => $relays, ]); }