clone of github.com/decent-newsroom/newsroom
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.
 
 
 
 
 
 

140 lines
5.8 KiB

<?php
declare(strict_types=1);
namespace App\Controller;
use App\Enum\KindsEnum;
use App\Service\NostrClient;
use App\Service\NostrLinkParser;
use App\Service\RedisCacheService;
use Exception;
use http\Client\Request;
use nostriphant\NIP19\Bech32;
use nostriphant\NIP19\Data;
use nostriphant\NIP19\Data\Note;
use Psr\Log\LoggerInterface;
use swentel\nostr\Key\Key;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
class EventController extends AbstractController
{
/**
* @throws Exception
*/
#[Route('/e/{nevent}', name: 'nevent', requirements: ['nevent' => '^(nevent|note)1.*'])]
public function index($nevent, \Symfony\Component\HttpFoundation\Request $request, NostrClient $nostrClient,
RedisCacheService $redisCacheService, NostrLinkParser $nostrLinkParser, LoggerInterface $logger): Response
{
$logger->info('Accessing event page', ['nevent' => $nevent]);
try {
// Decode nevent - nevent1... is a NIP-19 encoded event identifier
$decoded = new Bech32($nevent);
$logger->info('Decoded event', ['decoded' => json_encode($decoded)]);
// Get the event using the event ID
/** @var Data $data */
$data = $decoded->data;
$logger->info('Event data', ['data' => json_encode($data)]);
// Sort which event type this is using $data->type
switch ($decoded->type) {
case 'note':
// Handle note (regular event)
$event = $nostrClient->getEventById($data->data);
break;
case 'nprofile':
// Redirect to author profile if it's a profile identifier
$logger->info('Redirecting to author profile', ['pubkey' => $data->pubkey]);
return $this->redirectToRoute('author-redirect', ['pubkey' => $data->pubkey]);
case 'nevent':
// Handle nevent identifier (event with additional metadata)
$relays = $data->relays ?? null;
$event = $nostrClient->getEventById($data->id, $relays);
break;
case 'naddr':
// Handle naddr (parameterized replaceable event)
$decodedData = [
'kind' => $data->kind,
'pubkey' => $data->pubkey,
'identifier' => $data->identifier,
'relays' => $data->relays ?? []
];
$event = $nostrClient->getEventByNaddr($decodedData);
if ($data->kind === KindsEnum::LONGFORM->value) {
// If it's a long-form content, redirect to the article page
$logger->info('Redirecting to article', ['identifier' => $data->identifier]);
return $this->redirectToRoute('article-slug', ['slug' => $data->identifier]);
}
break;
default:
$logger->error('Unsupported event type', ['type' => $decoded->type]);
throw new NotFoundHttpException('Unsupported event type: ' . $decoded->type);
}
if (!$event) {
$logger->warning('Event not found', ['data' => $data]);
throw new NotFoundHttpException('Event not found');
}
// Parse event content for Nostr links
$nostrLinks = [];
if (isset($event->content)) {
$nostrLinks = $nostrLinkParser->parseLinks($event->content);
$logger->info('Parsed Nostr links from content', ['count' => count($nostrLinks)]);
}
$authorMetadata = $redisCacheService->getMetadata($event->pubkey);
// Batch fetch profiles for follow pack events (kind 39089)
$followPackProfiles = [];
if (isset($event->kind) && $event->kind == 39089 && isset($event->tags)) {
$pubkeys = [];
foreach ($event->tags as $tag) {
if (is_array($tag) && $tag[0] === 'p' && isset($tag[1])) {
$pubkeys[] = $tag[1];
}
}
if (!empty($pubkeys)) {
$logger->info('Batch fetching follow pack profiles', ['count' => count($pubkeys)]);
$followPackProfiles = $redisCacheService->getMultipleMetadata($pubkeys);
}
}
// Render template with the event data and extracted Nostr links
$response = $this->render('event/index.html.twig', [
'event' => $event,
'author' => $authorMetadata,
'nostrLinks' => $nostrLinks,
'followPackProfiles' => $followPackProfiles
]);
// Add HTTP caching headers for request-level caching
$response->setPublic(); // Allow public caching (browsers, CDNs)
$response->setMaxAge(300); // Cache for 5 minutes
$response->setSharedMaxAge(300); // Same for shared caches (CDNs)
// Add ETag for conditional requests
$etag = md5($nevent . ($event->created_at ?? '') . ($event->content ?? ''));
$response->setEtag($etag);
$response->setLastModified(new \DateTime('@' . ($event->created_at ?? time())));
// Check if client has current version
$response->isNotModified($request);
return $response;
} catch (Exception $e) {
$logger->error('Error processing event', ['error' => $e->getMessage()]);
throw $e;
}
}
}