Files
c-relay-pg/admin/api/live_subscription.php
T
Laan Tungir 50653dc86a v2.1.38 - Custom backfill feature: ad-hoc backfill jobs with arbitrary NIP-01 filters + admin UI isolation
- New caching_custom_backfill_jobs and caching_custom_backfill_batches tables
- Admin API (POST create/cancel, GET list) at admin/api/custom_backfill.php
- Admin UI form with preset buttons and job status table on Backfill page
- Caching daemon module (custom_backfill.c) processes jobs independently
- pg_inbox functions for job/batch claim, progress update, and cancel
- Three batch modes: author-batched, id-batched, time-window scan
- Fixed done_batches overcount on job completion
- Admin config now environment-variable driven (C_RELAY_DB_*)
- admin/serve.sh supports separate instances for different databases
- make_and_restart_relay.sh only kills relay on target port, not all relays
2026-08-07 14:49:13 -04:00

160 lines
7.5 KiB
PHP

<?php
/** admin2/api/live_subscription.php — Live subscription config API. */
require_once __DIR__ . '/../lib/helpers.php';
$pdo = db();
// --- POST actions: update live subscription config ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$input = json_decode(file_get_contents('php://input'), true) ?: [];
$action = $input['action'] ?? '';
if ($action === 'toggle_enabled') {
// Toggle caching_live_enabled. The relay's config-change listener
// starts/stops caching_relay based on live OR backfill settings.
try {
$current = $pdo->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.
// follow_count is computed on the fly from caching_backfill_relay_progress
// (the column in caching_relays is never populated by the daemon, so we
// LEFT JOIN to get an always-fresh count of followed authors per relay).
$relays = [];
try {
$relays = $pdo->query("
SELECT cr.relay_url, cr.live_enabled, cr.backfill_enabled,
cr.status_code, cr.status_text, cr.is_bootstrap,
COUNT(DISTINCT brp.author_pubkey) AS follow_count
FROM caching_relays cr
LEFT JOIN caching_backfill_relay_progress brp
ON brp.relay_url = cr.relay_url
GROUP BY cr.relay_url, cr.live_enabled, cr.backfill_enabled,
cr.status_code, cr.status_text, cr.is_bootstrap
ORDER BY follow_count DESC, cr.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,
]);
}