Browse Source

bug-fixes

imwald
Silberengel 1 week ago
parent
commit
11a930d764
  1. 3
      src/Controller/DefaultController.php
  2. 63
      src/Service/NostrClient.php
  3. 31
      src/Twig/Components/Molecules/CategoryLink.php
  4. 93
      src/Twig/Components/Organisms/FeaturedList.php
  5. 4
      templates/pages/category.html.twig

3
src/Controller/DefaultController.php

@ -116,6 +116,9 @@ class DefaultController extends AbstractController @@ -116,6 +116,9 @@ class DefaultController extends AbstractController
}
}
$category['title'] = $category['title'] ?? '';
$category['summary'] = $category['summary'] ?? '';
return $this->render('pages/category.html.twig', [
'list' => $list,
'category' => $category

63
src/Service/NostrClient.php

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
namespace App\Service;
use App\Entity\Article;
use App\Entity\Event as PublicationEventEntity;
use App\Enum\KindsEnum;
use App\Factory\ArticleFactory;
use Doctrine\ORM\EntityManagerInterface;
@ -815,11 +816,15 @@ class NostrClient @@ -815,11 +816,15 @@ class NostrClient
}
}
public function getMagazineIndex( $npub, $dTag)
/**
* Latest kind 30040 index for this author and #d tag, as {@see PublicationEventEntity}
* so callers can use {@see PublicationEventEntity::getTags()} (relay payloads are otherwise stdClass).
*/
public function getMagazineIndex(mixed $npub, mixed $dTag): ?PublicationEventEntity
{
$request = $this->createNostrRequest(
kinds: [KindsEnum::PUBLICATION_INDEX],
filters: ['authors' => [$npub], 'tag' => ['#d', [$dTag]]],
filters: ['authors' => [(string) $npub], 'tag' => ['#d', [(string) $dTag]]],
);
$response = $request->send();
$this->logger->info('Getting magazine index', ['npub' => $npub, 'dTag' => $dTag, 'response' => $response]);
@ -831,8 +836,56 @@ class NostrClient @@ -831,8 +836,56 @@ class NostrClient
$this->logger->warning('No magazine index found', ['npub' => $npub, 'dTag' => $dTag]);
return null;
}
// Sort by date and return the most recent
usort($events, fn($a, $b) => $b->created_at <=> $a->created_at);
return $events[0];
usort($events, static function ($a, $b): int {
return self::magazineEventCreatedAt($b) <=> self::magazineEventCreatedAt($a);
});
return self::magazineEventToPublicationEntity($events[0]);
}
private static function magazineEventCreatedAt(mixed $event): int
{
if ($event instanceof PublicationEventEntity) {
return $event->getCreatedAt();
}
if (\is_object($event) && isset($event->created_at)) {
return (int) $event->created_at;
}
return 0;
}
/**
* Normalize relay / library event objects to the app's Event entity (not persisted).
*/
private static function magazineEventToPublicationEntity(mixed $raw): ?PublicationEventEntity
{
if ($raw instanceof PublicationEventEntity) {
return $raw;
}
if (!\is_object($raw)) {
return null;
}
try {
/** @var array<string, mixed> $data */
$data = json_decode(json_encode($raw, \JSON_THROW_ON_ERROR), true, 512, \JSON_THROW_ON_ERROR);
} catch (\JsonException) {
return null;
}
if (!\is_array($data)) {
return null;
}
$entity = new PublicationEventEntity();
$entity->setId((string) ($data['id'] ?? ''));
$entity->setKind((int) ($data['kind'] ?? 0));
$entity->setPubkey((string) ($data['pubkey'] ?? ''));
$entity->setContent((string) ($data['content'] ?? ''));
$entity->setCreatedAt((int) ($data['created_at'] ?? 0));
$tags = $data['tags'] ?? [];
$entity->setTags(\is_array($tags) ? $tags : []);
$entity->setSig((string) ($data['sig'] ?? ''));
return $entity;
}
}

31
src/Twig/Components/Molecules/CategoryLink.php

@ -8,8 +8,9 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; @@ -8,8 +8,9 @@ use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
final class CategoryLink
{
public string $title;
public string $slug;
public string $title = '';
public string $slug = '';
public function __construct(private CacheInterface $cache)
{
@ -17,22 +18,28 @@ final class CategoryLink @@ -17,22 +18,28 @@ final class CategoryLink
public function mount($category): void
{
$parts = explode(':', $category[1]);
$this->slug = $parts[2];
$coord = $category[1] ?? '';
$parts = explode(':', (string) $coord, 3);
$this->slug = $parts[2] ?? '';
$this->title = $this->slug !== '' ? $this->slug : 'Category';
try {
$cat = $this->cache->get('magazine-' . $parts[2], function (){
throw new \Exception('Not found');
$cat = $this->cache->get('magazine-' . $this->slug, function () {
throw new \RuntimeException('Not found');
});
$tags = $cat->getTags();
$tags = method_exists($cat, 'getTags') ? $cat->getTags() : [];
$title = array_filter($tags, function($tag) {
return ($tag[0] === 'title');
$titleTags = array_filter($tags, static function ($tag): bool {
return isset($tag[0]) && $tag[0] === 'title' && isset($tag[1]);
});
$this->title = $title[array_key_first($title)][1];
} catch (\Exception $e) {
// Handle cache miss
$first = array_key_first($titleTags);
if ($first !== null) {
$this->title = (string) $titleTags[$first][1];
}
} catch (\Throwable) {
// Cache miss or unreadable index: keep slug-based fallback title
}
}
}

93
src/Twig/Components/Organisms/FeaturedList.php

@ -3,78 +3,105 @@ @@ -3,78 +3,105 @@
namespace App\Twig\Components\Organisms;
use App\Repository\ArticleRepository;
use App\Service\NostrClient;
use Psr\Cache\InvalidArgumentException;
use swentel\nostr\Event\Event;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
final class FeaturedList
{
public string $category;
public string $title;
public string $category = '';
public string $title = '';
public array $list = [];
public function __construct(
private readonly CacheInterface $cache,
private readonly ArticleRepository $articleRepository)
{
private readonly ArticleRepository $articleRepository,
private readonly NostrClient $nostrClient,
private readonly ParameterBagInterface $params,
) {
}
/**
* @throws InvalidArgumentException
* @throws \Exception
*/
public function mount($category): void
{
$parts = explode(':', $category[1]);
/** @var Event $catIndex */
$catIndex = $this->cache->get('magazine-' . $parts[2], function (){
throw new \Exception('Not found');
$this->list = [];
$this->title = '';
$coord = $category[1] ?? '';
$this->category = (string) $coord;
$parts = explode(':', $this->category, 3);
if (\count($parts) < 3) {
return;
}
$slug = $parts[2];
$npub = (string) $this->params->get('npub');
try {
$catIndex = $this->cache->get('magazine-' . $slug, function (ItemInterface $item) use ($npub, $slug) {
$item->expiresAfter(300);
$mag = $this->nostrClient->getMagazineIndex($npub, $slug);
if ($mag === null) {
throw new \RuntimeException('Category index not found for '.$slug);
}
return $mag;
});
} catch (\Throwable) {
return;
}
$slugs = [];
foreach ($catIndex->getTags() as $tag) {
if ($tag[0] === 'title') {
$this->title = $tag[1];
if (($tag[0] ?? null) === 'title' && isset($tag[1])) {
$this->title = (string) $tag[1];
}
if (($tag[0] ?? null) === 'a' && isset($tag[1])) {
$segs = explode(':', (string) $tag[1], 3);
$slugs[] = end($segs);
if (\count($slugs) >= 5) {
break;
}
}
if ($tag[0] === 'a') {
$parts = explode(':', $tag[1], 3);
$slugs[] = end($parts);
if (count($slugs) >= 5) {
break; // Limit to 5 items
}
if ($this->title === '') {
$this->title = $slug;
}
if ($slugs === []) {
return;
}
// Use database query instead of Elasticsearch
if (!empty($slugs)) {
$articles = $this->articleRepository->findBySlugsCriteria($slugs);
// Create a map of slug => item to get the latest version of each
$slugMap = [];
foreach ($articles as $article) {
$slug = $article->getSlug();
if ($slug !== '') {
if (!isset($slugMap[$slug])) {
$slugMap[$slug] = $article;
} elseif ($article->getCreatedAt() > $slugMap[$slug]->getCreatedAt()) {
$slugMap[$slug] = $article;
$articleSlug = $article->getSlug();
if ($articleSlug !== '') {
if (!isset($slugMap[$articleSlug])) {
$slugMap[$articleSlug] = $article;
} elseif ($article->getCreatedAt() > $slugMap[$articleSlug]->getCreatedAt()) {
$slugMap[$articleSlug] = $article;
}
}
}
// Build ordered list based on original slugs order
$orderedList = [];
foreach ($slugs as $slug) {
if (isset($slugMap[$slug])) {
$orderedList[] = $slugMap[$slug];
foreach ($slugs as $articleSlug) {
if (isset($slugMap[$articleSlug])) {
$orderedList[] = $slugMap[$articleSlug];
}
}
$this->list = array_slice($orderedList, 0, 4);
} else {
$this->list = [];
}
}
}

4
templates/pages/category.html.twig

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
{% extends 'base.html.twig' %}
{% block ogtags %}
<meta property="og:title" content="{{ category.title }}">
<meta property="og:title" content="{{ category.title|default('') }}">
<meta property="og:type" content="website">
<meta property="og:url" content="{{ app.request.uri }}">
<meta property="og:description" content="{{ category.summary }}">
<meta property="og:description" content="{{ category.summary|default('') }}">
<meta property="og:image" content="{{ absolute_url(asset('og-image.jpg')) }}">
<meta property="og:site_name" content="{{ website_name }}">
{% endblock %}

Loading…
Cancel
Save