- 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
160 lines
7.5 KiB
PHP
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,
|
|
]);
|
|
}
|