You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
275 lines
8.2 KiB
275 lines
8.2 KiB
<?php |
|
|
|
declare(strict_types=1); |
|
|
|
namespace App\Service; |
|
|
|
use Psr\Log\LoggerInterface; |
|
use swentel\nostr\Filter\Filter; |
|
use swentel\nostr\Message\RequestMessage; |
|
use swentel\nostr\Relay\Relay; |
|
use swentel\nostr\Request\Request; |
|
use swentel\nostr\Subscription\Subscription; |
|
|
|
/** |
|
* Service to interact with the strfry relay |
|
*/ |
|
class RelayAdminService |
|
{ |
|
public function __construct( |
|
private readonly LoggerInterface $logger, |
|
private readonly ?string $nostrDefaultRelay = null |
|
) { |
|
} |
|
|
|
/** |
|
* Get relay statistics by actually querying the relay |
|
*/ |
|
public function getStats(): array |
|
{ |
|
try { |
|
$relayUrl = $this->nostrDefaultRelay ?? 'ws://strfry:7777'; |
|
|
|
// Test if relay is accessible |
|
if (!$this->testRelayConnection($relayUrl)) { |
|
return [ |
|
'error' => 'Cannot connect to relay at ' . $relayUrl, |
|
'total_events' => 0, |
|
'relay_accessible' => false |
|
]; |
|
} |
|
|
|
// Try to count events by querying with a limit |
|
$eventCount = $this->estimateEventCount($relayUrl); |
|
|
|
// Format the event count message |
|
if ($eventCount >= 100) { |
|
$displayCount = '100+ (many events - use CLI for exact count)'; |
|
} elseif ($eventCount > 0) { |
|
$displayCount = $eventCount; |
|
} else { |
|
$displayCount = 0; |
|
} |
|
|
|
return [ |
|
'total_events' => $displayCount, |
|
'relay_accessible' => true, |
|
'database_size' => '~800 MB (from docker volume)', |
|
'info' => 'Sample of ' . $eventCount . ' events retrieved. Use CLI for full statistics.' |
|
]; |
|
} catch (\Exception $e) { |
|
$this->logger->error('Failed to get relay stats', ['error' => $e->getMessage()]); |
|
return ['error' => $e->getMessage()]; |
|
} |
|
} |
|
|
|
/** |
|
* Get recent events from relay by actually querying it |
|
*/ |
|
public function getRecentEvents(int $limit = 10): array |
|
{ |
|
try { |
|
$relayUrl = $this->nostrDefaultRelay ?? 'ws://strfry:7777'; |
|
|
|
// Create relay connection |
|
$relay = new Relay($relayUrl); |
|
|
|
// Create subscription |
|
$subscription = new Subscription(); |
|
$subscriptionId = $subscription->setId(); |
|
|
|
// Create filter for recent events (kind 30023 - articles) |
|
$filter = new Filter(); |
|
$filter->setKinds([30023, 1, 7, 0]); // Articles, notes, reactions, profiles |
|
$filter->setLimit($limit); |
|
|
|
// Create and send request |
|
$requestMessage = new RequestMessage($subscriptionId, [$filter]); |
|
$request = new Request($relay, $requestMessage); |
|
|
|
// Get response with timeout |
|
$response = $request->send(); |
|
|
|
$events = []; |
|
if (is_array($response) && !empty($response)) { |
|
foreach ($response as $relayResponse) { |
|
if (is_array($relayResponse)) { |
|
foreach ($relayResponse as $item) { |
|
if (isset($item->type) && $item->type === 'EVENT' && isset($item->event)) { |
|
$events[] = (array)$item->event; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return array_slice($events, 0, $limit); |
|
} catch (\Exception $e) { |
|
$this->logger->error('Failed to get recent events', ['error' => $e->getMessage()]); |
|
return []; |
|
} |
|
} |
|
|
|
/** |
|
* Estimate event count by querying the relay |
|
*/ |
|
private function estimateEventCount(string $relayUrl): int |
|
{ |
|
try { |
|
$relay = new Relay($relayUrl); |
|
$subscription = new Subscription(); |
|
$subscriptionId = $subscription->setId(); |
|
|
|
// Query for a sample to check if relay has events |
|
$filter = new Filter(); |
|
$filter->setLimit(100); |
|
|
|
$requestMessage = new RequestMessage($subscriptionId, [$filter]); |
|
$request = new Request($relay, $requestMessage); |
|
$response = $request->send(); |
|
|
|
$count = 0; |
|
if (is_array($response) && !empty($response)) { |
|
foreach ($response as $relayResponse) { |
|
if (is_array($relayResponse)) { |
|
foreach ($relayResponse as $item) { |
|
if (isset($item->type) && $item->type === 'EVENT') { |
|
$count++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return $count; |
|
} catch (\Exception $e) { |
|
return 0; |
|
} |
|
} |
|
|
|
/** |
|
* Get relay container status by checking connectivity |
|
*/ |
|
public function getContainerStatus(): array |
|
{ |
|
$strfryStatus = $this->checkServiceHealth('strfry', 7777); |
|
$ingestStatus = ['status' => 'unknown', 'health' => 'Cannot check from inside container']; |
|
|
|
return [ |
|
'strfry' => $strfryStatus, |
|
'ingest' => $ingestStatus, |
|
]; |
|
} |
|
|
|
/** |
|
* Get relay configuration from environment |
|
*/ |
|
public function getConfiguration(): array |
|
{ |
|
return [ |
|
'relay_url' => $this->nostrDefaultRelay ?? 'Not configured', |
|
'relay_internal' => 'ws://strfry:7777', |
|
'relay_external' => 'ws://localhost:7777', |
|
'upstreams' => $_ENV['RELAY_UPSTREAMS'] ?? 'Not configured', |
|
'days_articles' => $_ENV['RELAY_DAYS_ARTICLES'] ?? '7', |
|
'days_threads' => $_ENV['RELAY_DAYS_THREADS'] ?? '3', |
|
]; |
|
} |
|
|
|
/** |
|
* Test relay connectivity |
|
*/ |
|
public function testConnectivity(): array |
|
{ |
|
$relayUrl = $this->nostrDefaultRelay ?? 'ws://strfry:7777'; |
|
$isAccessible = $this->testRelayConnection($relayUrl); |
|
|
|
return [ |
|
'container_running' => $isAccessible, |
|
'port_accessible' => $isAccessible, |
|
'relay_url' => $relayUrl, |
|
]; |
|
} |
|
|
|
/** |
|
* Test if we can connect to the relay |
|
*/ |
|
private function testRelayConnection(string $url): bool |
|
{ |
|
try { |
|
// Parse URL to get host and port |
|
$parts = parse_url($url); |
|
if (!$parts || !isset($parts['host'])) { |
|
return false; |
|
} |
|
|
|
$host = $parts['host']; |
|
$port = $parts['port'] ?? 7777; |
|
|
|
// Try to open a socket connection |
|
$socket = @fsockopen($host, $port, $errno, $errstr, 2); |
|
|
|
if ($socket) { |
|
fclose($socket); |
|
return true; |
|
} |
|
|
|
return false; |
|
} catch (\Exception $e) { |
|
$this->logger->error('Relay connection test failed', [ |
|
'url' => $url, |
|
'error' => $e->getMessage() |
|
]); |
|
return false; |
|
} |
|
} |
|
|
|
/** |
|
* Check service health by testing port connectivity |
|
*/ |
|
private function checkServiceHealth(string $host, int $port): array |
|
{ |
|
$isRunning = $this->testPortOpen($host, $port); |
|
|
|
return [ |
|
'status' => $isRunning ? 'running' : 'not running', |
|
'health' => $isRunning ? 'healthy' : 'unhealthy', |
|
'name' => $host, |
|
'port' => $port, |
|
'method' => 'socket_test' |
|
]; |
|
} |
|
|
|
/** |
|
* Test if a port is open |
|
*/ |
|
private function testPortOpen(string $host, int $port): bool |
|
{ |
|
$socket = @fsockopen($host, $port, $errno, $errstr, 2); |
|
if ($socket) { |
|
fclose($socket); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
/** |
|
* Trigger manual sync - placeholder |
|
*/ |
|
public function triggerSync(): array |
|
{ |
|
return [ |
|
'success' => false, |
|
'message' => 'Manual sync trigger not available from web interface. Use CLI: make relay-ingest-now', |
|
]; |
|
} |
|
|
|
/** |
|
* Get recent sync logs - placeholder |
|
*/ |
|
public function getSyncLogs(int $lines = 50): string |
|
{ |
|
return 'Log viewing not available from web interface. Use CLI: docker compose logs ingest'; |
|
} |
|
} |
|
|
|
|