Browse Source

bug-fixes

gitcitadel
Silberengel 2 weeks ago
parent
commit
c967cf1df5
  1. 3
      config/services.yaml
  2. 11
      src/Command/PrewarmCommand.php
  3. 31
      src/Repository/EventRepository.php
  4. 13
      src/Service/ArticleBodyHighlightInjector.php
  5. 158
      src/Service/CacheService.php
  6. 14
      src/Service/FeaturedAuthorListedRows.php
  7. 5
      src/Service/HighlightAuthorMetadataProvider.php
  8. 32
      src/Service/NostrClient.php
  9. 22
      src/Service/NostrWireEventMerge.php
  10. 42
      src/Twig/ArticleCardCoverExtension.php
  11. 1
      templates/components/Organisms/FeaturedList.html.twig
  12. 1
      templates/components/Organisms/FeaturedWall.html.twig
  13. 1
      templates/components/Organisms/HomeMagazineArticleStrip.html.twig
  14. 1
      tests/Service/ArticleBodyHighlightInjectorTest.php
  15. 1
      tests/Service/ArticleHighlightCommonMarkPipelineTest.php
  16. 24
      tests/Service/NostrWireEventMergeTest.php

3
config/services.yaml

@ -77,6 +77,9 @@ services:
App\Service\Nip05VerificationService: App\Service\Nip05VerificationService:
arguments: arguments:
$appCache: '@cache.app' $appCache: '@cache.app'
App\Service\CacheService:
tags:
- { name: kernel.reset, method: reset }
when@test: when@test:
services: services:

11
src/Command/PrewarmCommand.php

@ -379,6 +379,17 @@ final class PrewarmCommand extends Command
$this->waitForSiteWellKnownBeforeVerification($io, $domain); $this->waitForSiteWellKnownBeforeVerification($io, $domain);
} }
$io->writeln('Verifying <comment>NIP-05</comment> (HTTPS <comment>/.well-known/nostr.json</comment>, per identifier)…'); $io->writeln('Verifying <comment>NIP-05</comment> (HTTPS <comment>/.well-known/nostr.json</comment>, per identifier)…');
$npubsForVerify = [];
foreach ($toWarm as $hex) {
if (64 !== \strlen($hex) || !ctype_xdigit($hex)) {
continue;
}
try {
$npubsForVerify[] = $this->nostrKeyHelper->convertPublicKeyToBech32(strtolower($hex));
} catch (\Throwable) {
}
}
$this->cacheService->prefetchMetadataForNpubs($npubsForVerify);
$nt = 0; $nt = 0;
$nv = 0; $nv = 0;
foreach ($toWarm as $hex) { foreach ($toWarm as $hex) {

31
src/Repository/EventRepository.php

@ -22,4 +22,35 @@ class EventRepository extends ServiceEntityRepository
{ {
return $this->findOneBy(['coreRowKey' => $key]); return $this->findOneBy(['coreRowKey' => $key]);
} }
/**
* @param list<string> $keys
*
* @return array<string, Event> keyed by coreRowKey
*/
public function findByCoreRowKeys(array $keys): array
{
$keys = array_values(array_unique(array_filter(
$keys,
static fn (mixed $k): bool => \is_string($k) && $k !== '',
)));
if ($keys === []) {
return [];
}
/** @var list<Event> $rows */
$rows = $this->createQueryBuilder('e')
->andWhere('e.coreRowKey IN (:keys)')
->setParameter('keys', $keys)
->getQuery()
->getResult();
$out = [];
foreach ($rows as $row) {
$k = $row->getCoreRowKey();
if ($k !== null && $k !== '') {
$out[$k] = $row;
}
}
return $out;
}
} }

13
src/Service/ArticleBodyHighlightInjector.php

@ -338,6 +338,19 @@ final class ArticleBodyHighlightInjector
*/ */
private function buildHighlightAuthorsJson(array $group): string private function buildHighlightAuthorsJson(array $group): string
{ {
$npubsForPrefetch = [];
foreach ($group as $h) {
$pk = $h->getAuthorPubkey();
if (64 !== \strlen($pk) || !ctype_xdigit($pk)) {
continue;
}
try {
$npubsForPrefetch[] = $this->nostrKeyHelper->convertPublicKeyToBech32($pk);
} catch (\Throwable) {
}
}
$this->highlightAuthorMetadata->prefetchMetadataForNpubs($npubsForPrefetch);
$byNpub = []; $byNpub = [];
foreach ($group as $h) { foreach ($group as $h) {
$eidH = $h->getEventId(); $eidH = $h->getEventId();

158
src/Service/CacheService.php

@ -9,9 +9,20 @@ use App\Nostr\MagazineEventKeys;
use App\Repository\EventRepository; use App\Repository\EventRepository;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Contracts\Service\ResetInterface;
readonly class CacheService implements HighlightAuthorMetadataProvider final class CacheService implements HighlightAuthorMetadataProvider, ResetInterface
{ {
/**
* @var array<string, array{content: \stdClass, kind0_tags: list<list<string>>, nip30_custom_emojis: list<array{shortcode: string, url: string, set?: string}>}>
*/
private array $requestBundlesByHex = [];
/** @var array<string, string> lowercase hex pubkey => npub */
private array $pendingHexToNpub = [];
private int $metadataBatchDepth = 0;
public function __construct( public function __construct(
private NostrClient $nostrClient, private NostrClient $nostrClient,
private EntityManagerInterface $entityManager, private EntityManagerInterface $entityManager,
@ -23,6 +34,13 @@ readonly class CacheService implements HighlightAuthorMetadataProvider
) { ) {
} }
public function reset(): void
{
$this->requestBundlesByHex = [];
$this->pendingHexToNpub = [];
$this->metadataBatchDepth = 0;
}
public function getMetadata(string $npub): \stdClass public function getMetadata(string $npub): \stdClass
{ {
return $this->getMetadataBundle($npub)['content']; return $this->getMetadataBundle($npub)['content'];
@ -37,41 +55,129 @@ readonly class CacheService implements HighlightAuthorMetadataProvider
if ($authorHex === null) { if ($authorHex === null) {
return $this->placeholderMetadataBundle($npub); return $this->placeholderMetadataBundle($npub);
} }
if (isset($this->requestBundlesByHex[$authorHex])) {
return $this->requestBundlesByHex[$authorHex];
}
$row = $this->eventRepository->findOneByCoreRowKey(MagazineEventKeys::profileKind0($authorHex)); $row = $this->eventRepository->findOneByCoreRowKey(MagazineEventKeys::profileKind0($authorHex));
if ($row !== null) { if ($row !== null) {
return $this->bundleFromKind0EventRow($row, $npub); $bundle = $this->bundleFromKind0EventRow($row, $npub);
$this->requestBundlesByHex[$authorHex] = $bundle;
return $bundle;
} }
try { $this->pendingHexToNpub[$authorHex] = $npub;
$ev = $this->nostrClient->getNpubMetadata($npub); $this->runPendingMetadataBatch();
if (!\is_object($ev)) {
return $this->placeholderMetadataBundle($npub); return $this->requestBundlesByHex[$authorHex] ?? $this->placeholderMetadataBundle($npub);
}
/**
* @param list<string> $npubs
*/
public function prefetchMetadataForNpubs(array $npubs): void
{
foreach ($npubs as $npub) {
if (!\is_string($npub) || $npub === '') {
continue;
} }
$nip30 = $this->nip30EmojiCatalogBuilder->buildMergedCatalog($ev, null, []); $authorHex = $this->npubToAuthorHex64($npub);
$this->replaceByCoreKey( if ($authorHex === null || isset($this->requestBundlesByHex[$authorHex])) {
MagazineEventKeys::profileKind0($authorHex), continue;
Event::STORAGE_PROFILE_KIND0, }
$ev, $this->pendingHexToNpub[$authorHex] = $npub;
$nip30, }
); $this->runPendingMetadataBatch();
$tags = self::normalizeEventTagsList($ev->tags ?? null); }
$content = $this->decodeKind0ContentObject($ev);
if ($this->isPlaceholderContent($content, $npub)) { /**
$content = $this->namePlaceholderNpubObject($npub); * @param list<string> $pubkeyHex 64-char hex pubkeys (any case)
*/
public function prefetchMetadataForPubkeyHexes(array $pubkeyHex): void
{
$npubs = [];
foreach ($pubkeyHex as $hex) {
if (!\is_string($hex) || 64 !== \strlen($hex) || !ctype_xdigit($hex)) {
continue;
} }
try {
$npubs[] = $this->nostrKeyHelper->convertPublicKeyToBech32(strtolower($hex));
} catch (\Throwable) {
}
}
$this->prefetchMetadataForNpubs($npubs);
}
private function runPendingMetadataBatch(): void
{
if ($this->pendingHexToNpub === []) {
return;
}
++$this->metadataBatchDepth;
try {
do {
if ($this->pendingHexToNpub === []) {
break;
}
$this->flushPendingMetadataFetches();
} while ($this->pendingHexToNpub !== []);
} finally {
--$this->metadataBatchDepth;
}
}
private function flushPendingMetadataFetches(): void
{
if ($this->pendingHexToNpub === []) {
return;
}
$pending = $this->pendingHexToNpub;
$this->pendingHexToNpub = [];
return [ $keys = [];
'content' => $content, foreach (array_keys($pending) as $hex) {
'kind0_tags' => $tags, $keys[] = MagazineEventKeys::profileKind0($hex);
'nip30_custom_emojis' => $nip30, }
]; $rowsByKey = $this->eventRepository->findByCoreRowKeys($keys);
} catch (\Exception $e) {
$relayHex = [];
foreach ($pending as $hex => $npub) {
$key = MagazineEventKeys::profileKind0($hex);
if (isset($rowsByKey[$key])) {
$this->requestBundlesByHex[$hex] = $this->bundleFromKind0EventRow($rowsByKey[$key], $npub);
continue;
}
$relayHex[] = $hex;
}
if ($relayHex === []) {
return;
}
try {
$fetched = $this->nostrClient->fetchProfilePrewarmWireBundlesForAuthors($relayHex);
$this->putPrewarmMetadataBatch($relayHex, $fetched);
} catch (\Throwable $e) {
$this->logger->warning('Profile metadata batch fetch failed.', [
'authors' => \count($relayHex),
'exception' => $e,
]);
}
$rowsAfterRelay = $this->eventRepository->findByCoreRowKeys(array_map(
static fn (string $hex): string => MagazineEventKeys::profileKind0($hex),
$relayHex,
));
foreach ($relayHex as $hex) {
$npub = $pending[$hex];
$key = MagazineEventKeys::profileKind0($hex);
if (isset($rowsAfterRelay[$key])) {
$this->requestBundlesByHex[$hex] = $this->bundleFromKind0EventRow($rowsAfterRelay[$key], $npub);
continue;
}
$this->logger->warning('Profile metadata fetch failed; using npub placeholder.', [ $this->logger->warning('Profile metadata fetch failed; using npub placeholder.', [
'npub' => $npub, 'npub' => $npub,
'exception' => $e->getPrevious() ?? $e,
]); ]);
$this->requestBundlesByHex[$hex] = $this->placeholderMetadataBundle($npub);
} }
return $this->placeholderMetadataBundle($npub);
} }
/** /**

14
src/Service/FeaturedAuthorListedRows.php

@ -33,9 +33,11 @@ final class FeaturedAuthorListedRows
return $fromDb; return $fromDb;
} }
$hexes = \array_slice($this->magazineContent->getAllDistinctCategoryAuthorPubkeyHexes(), 0, $limit);
$this->cacheService->prefetchMetadataForPubkeyHexes($hexes);
$authors = []; $authors = [];
$hexes = $this->magazineContent->getAllDistinctCategoryAuthorPubkeyHexes(); foreach ($hexes as $hex) {
foreach (\array_slice($hexes, 0, $limit) as $hex) {
try { try {
$npub = $this->nostrKeyHelper->convertPublicKeyToBech32($hex); $npub = $this->nostrKeyHelper->convertPublicKeyToBech32($hex);
} catch (\Throwable) { } catch (\Throwable) {
@ -52,8 +54,14 @@ final class FeaturedAuthorListedRows
*/ */
public function buildListedByLocalPartPage(int $limit, int $offset = 0): array public function buildListedByLocalPartPage(int $limit, int $offset = 0): array
{ {
$listed = $this->featuredAuthorRepository->findListedOrderByLocalPartPaginated($limit, $offset);
$this->cacheService->prefetchMetadataForPubkeyHexes(array_map(
static fn ($fa) => $fa->getPubkeyHex(),
$listed,
));
$authors = []; $authors = [];
foreach ($this->featuredAuthorRepository->findListedOrderByLocalPartPaginated($limit, $offset) as $fa) { foreach ($listed as $fa) {
try { try {
$npub = $this->nostrKeyHelper->convertPublicKeyToBech32($fa->getPubkeyHex()); $npub = $this->nostrKeyHelper->convertPublicKeyToBech32($fa->getPubkeyHex());
} catch (\Throwable) { } catch (\Throwable) {

5
src/Service/HighlightAuthorMetadataProvider.php

@ -10,4 +10,9 @@ namespace App\Service;
interface HighlightAuthorMetadataProvider interface HighlightAuthorMetadataProvider
{ {
public function getMetadata(string $npub): \stdClass; public function getMetadata(string $npub): \stdClass;
/**
* @param list<string> $npubs
*/
public function prefetchMetadataForNpubs(array $npubs): void;
} }

32
src/Service/NostrClient.php

@ -386,6 +386,10 @@ class NostrClient
*/ */
public function getNpubMetadata($npub): \stdClass public function getNpubMetadata($npub): \stdClass
{ {
$authorHex = $this->wireMerge->authorIdentToHexLower($npub);
if ($authorHex === null) {
throw new \Exception('Invalid npub for metadata: '.$npub);
}
$relaysTried = $this->relayListFactory->capSequentialRelaysForProfileFetches($this->relayListFactory->getProfileMetadataQueryRelayUrlList()); $relaysTried = $this->relayListFactory->capSequentialRelaysForProfileFetches($this->relayListFactory->getProfileMetadataQueryRelayUrlList());
$relaysTriedStr = implode(', ', array_map(NostrRelayQuery::relayLogLabel(...), $relaysTried)); $relaysTriedStr = implode(', ', array_map(NostrRelayQuery::relayLogLabel(...), $relaysTried));
$relaySet = $this->relayListFactory->relaySetFromDistinctUrlList($relaysTried); $relaySet = $this->relayListFactory->relaySetFromDistinctUrlList($relaysTried);
@ -393,7 +397,7 @@ class NostrClient
$request = $this->nostrRelayQuery->createNostrRequest( $request = $this->nostrRelayQuery->createNostrRequest(
defaultRelaySet: $this->defaultRelaySet, defaultRelaySet: $this->defaultRelaySet,
kinds: [KindsEnum::METADATA], kinds: [KindsEnum::METADATA],
filters: ['authors' => [$npub]], filters: ['authors' => [$authorHex]],
relaySet: $relaySet relaySet: $relaySet
); );
@ -410,10 +414,6 @@ class NostrClient
throw new \Exception('No metadata for npub '.$npub.' (relays: '.$relaysTriedStr.')'); throw new \Exception('No metadata for npub '.$npub.' (relays: '.$relaysTriedStr.')');
} }
$byAddr = $this->wireMerge->mergeKind0EventsByReplaceableAddress($events); $byAddr = $this->wireMerge->mergeKind0EventsByReplaceableAddress($events);
$authorHex = $this->wireMerge->npubToHexPubkey($npub);
if ($authorHex === null) {
throw new \Exception('Invalid npub for metadata: '.$npub);
}
$key = '0:'.$authorHex; $key = '0:'.$authorHex;
if (!isset($byAddr[$key])) { if (!isset($byAddr[$key])) {
throw new \Exception('No kind-0 metadata for npub '.$npub.' (relays: '.$relaysTriedStr.')'); throw new \Exception('No kind-0 metadata for npub '.$npub.' (relays: '.$relaysTriedStr.')');
@ -431,6 +431,10 @@ class NostrClient
*/ */
public function getKind10133PaymentTargetEventsForNpub(string $npub, int $limit = 20): array public function getKind10133PaymentTargetEventsForNpub(string $npub, int $limit = 20): array
{ {
$authorHex = $this->wireMerge->authorIdentToHexLower($npub);
if ($authorHex === null) {
return [];
}
$relaysTried = $this->relayListFactory->capSequentialRelaysForProfileFetches($this->relayListFactory->getProfileMetadataQueryRelayUrlList()); $relaysTried = $this->relayListFactory->capSequentialRelaysForProfileFetches($this->relayListFactory->getProfileMetadataQueryRelayUrlList());
$relaysTriedStr = implode(', ', array_map(NostrRelayQuery::relayLogLabel(...), $relaysTried)); $relaysTriedStr = implode(', ', array_map(NostrRelayQuery::relayLogLabel(...), $relaysTried));
$relaySet = $this->relayListFactory->relaySetFromDistinctUrlList($relaysTried); $relaySet = $this->relayListFactory->relaySetFromDistinctUrlList($relaysTried);
@ -438,7 +442,7 @@ class NostrClient
$request = $this->nostrRelayQuery->createNostrRequest( $request = $this->nostrRelayQuery->createNostrRequest(
defaultRelaySet: $this->defaultRelaySet, defaultRelaySet: $this->defaultRelaySet,
kinds: [KindsEnum::PAYMENT_TARGETS], kinds: [KindsEnum::PAYMENT_TARGETS],
filters: ['authors' => [$npub], 'limit' => max(1, min(50, $limit))], filters: ['authors' => [$authorHex], 'limit' => max(1, min(50, $limit))],
relaySet: $relaySet relaySet: $relaySet
); );
$events = $this->nostrRelayQuery->processResponse( $events = $this->nostrRelayQuery->processResponse(
@ -463,11 +467,17 @@ class NostrClient
public function getNpubLongForm($npub): void public function getNpubLongForm($npub): void
{ {
$authorHex = $this->wireMerge->authorIdentToHexLower($npub);
if ($authorHex === null) {
$this->logger->warning('nostr.longform_by_author.invalid_npub', ['npub' => $npub]);
return;
}
$subscription = new Subscription(); $subscription = new Subscription();
$subscriptionId = $subscription->setId(); $subscriptionId = $subscription->setId();
$filter = new Filter(); $filter = new Filter();
$filter->setKinds([KindsEnum::LONGFORM]); $filter->setKinds([KindsEnum::LONGFORM]);
$filter->setAuthors([$npub]); $filter->setAuthors([$authorHex]);
$filter->setSince(strtotime('-6 months')); // too much? $filter->setSince(strtotime('-6 months')); // too much?
$requestMessage = new RequestMessage($subscriptionId, [$filter]); $requestMessage = new RequestMessage($subscriptionId, [$filter]);
@ -843,10 +853,14 @@ class NostrClient
*/ */
public function getNpubRelayList10002Wire($npub): ?object public function getNpubRelayList10002Wire($npub): ?object
{ {
$authorHex = $this->wireMerge->authorIdentToHexLower($npub);
if ($authorHex === null) {
return null;
}
$request = $this->nostrRelayQuery->createNostrRequest( $request = $this->nostrRelayQuery->createNostrRequest(
defaultRelaySet: $this->defaultRelaySet, defaultRelaySet: $this->defaultRelaySet,
kinds: [KindsEnum::RELAY_LIST], kinds: [KindsEnum::RELAY_LIST],
filters: ['authors' => [$npub]], filters: ['authors' => [$authorHex]],
relaySet: $this->defaultRelaySet relaySet: $this->defaultRelaySet
); );
$response = $this->nostrRelayQuery->processResponse($request->send(), function ($received) { $response = $this->nostrRelayQuery->processResponse($request->send(), function ($received) {
@ -1713,7 +1727,7 @@ class NostrClient
defaultRelaySet: $this->defaultRelaySet, defaultRelaySet: $this->defaultRelaySet,
relaySet: $relaySet, relaySet: $relaySet,
kinds: [KindsEnum::PUBLICATION_INDEX], kinds: [KindsEnum::PUBLICATION_INDEX],
filters: ['authors' => [(string) $npub], 'tag' => ['#d', [(string) $dTag]]], filters: ['authors' => [$authorHex], 'tag' => ['#d', [(string) $dTag]]],
); );
$this->logger->info(sprintf('Magazine index query (relays: %s)', $relaysForLog), [ $this->logger->info(sprintf('Magazine index query (relays: %s)', $relaysForLog), [
'npub' => $npub, 'npub' => $npub,

22
src/Service/NostrWireEventMerge.php

@ -375,16 +375,18 @@ final readonly class NostrWireEventMerge
if ($raw instanceof PublicationEventEntity) { if ($raw instanceof PublicationEventEntity) {
return $raw; return $raw;
} }
if (!\is_object($raw)) { if (\is_array($raw)) {
return null; $data = $raw;
} } elseif (\is_object($raw)) {
try {
try { $data = json_decode(json_encode($raw, \JSON_THROW_ON_ERROR), true, 512, \JSON_THROW_ON_ERROR);
$data = json_decode(json_encode($raw, \JSON_THROW_ON_ERROR), true, 512, \JSON_THROW_ON_ERROR); } catch (\JsonException) {
} catch (\JsonException) { return null;
return null; }
} if (!\is_array($data)) {
if (!\is_array($data)) { return null;
}
} else {
return null; return null;
} }
$entity = new PublicationEventEntity(); $entity = new PublicationEventEntity();

42
src/Twig/ArticleCardCoverExtension.php

@ -42,11 +42,34 @@ final class ArticleCardCoverExtension extends AbstractExtension
{ {
return [ return [
new TwigFunction('article_card_cover', $this->articleCardCover(...)), new TwigFunction('article_card_cover', $this->articleCardCover(...)),
new TwigFunction('prefetch_article_card_covers', $this->prefetchArticleCardCovers(...)),
new TwigFunction('article_og_image', $this->articleOgImage(...)), new TwigFunction('article_og_image', $this->articleOgImage(...)),
new TwigFunction('site_og_image', $this->siteOgImage(...)), new TwigFunction('site_og_image', $this->siteOgImage(...)),
]; ];
} }
/**
* Batch kind-0 profile lookups before a list of cards (one relay REQ per chunk, not per tile).
*
* @param iterable<mixed> $items Rows with optional `pubkey` (64-char hex)
*/
public function prefetchArticleCardCovers(iterable $items): void
{
$hexes = [];
foreach ($items as $item) {
if (\is_object($item) && isset($item->article)) {
$item = $item->article;
} elseif (\is_array($item) && isset($item['article'])) {
$item = $item['article'];
}
$hex = $this->pubkeyHexFromItem($item);
if ($hex !== null) {
$hexes[] = $hex;
}
}
$this->cacheService->prefetchMetadataForPubkeyHexes($hexes);
}
/** /**
* Branded site Open Graph image (home, category lists, base layout default): not tied to any article or author. * Branded site Open Graph image (home, category lists, base layout default): not tied to any article or author.
* *
@ -167,4 +190,23 @@ final class ArticleCardCoverExtension extends AbstractExtension
{ {
return $this->packages->getUrl(self::DEFAULT_PACKAGE_IMAGE); return $this->packages->getUrl(self::DEFAULT_PACKAGE_IMAGE);
} }
private function pubkeyHexFromItem(mixed $item): ?string
{
$raw = null;
if (\is_object($item) && isset($item->pubkey)) {
$raw = $item->pubkey;
} elseif (\is_array($item) && isset($item['pubkey'])) {
$raw = $item['pubkey'];
}
if (!\is_string($raw)) {
return null;
}
$hex = strtolower(trim($raw));
if (64 !== \strlen($hex) || !ctype_xdigit($hex)) {
return null;
}
return $hex;
}
} }

1
templates/components/Organisms/FeaturedList.html.twig

@ -6,6 +6,7 @@
role="region" role="region"
aria-label="{{ title|e('html_attr') }}" aria-label="{{ title|e('html_attr') }}"
> >
{% do prefetch_article_card_covers(list) %}
{% for item in list %} {% for item in list %}
<article class="featured-tile" style="--tile-hue: {{ _hue }};"> <article class="featured-tile" style="--tile-hue: {{ _hue }};">
<a <a

1
templates/components/Organisms/FeaturedWall.html.twig

@ -7,6 +7,7 @@
role="region" role="region"
aria-label="{{ (region_aria_label|default(website_name ~ ' — featured articles'))|e('html_attr') }}" aria-label="{{ (region_aria_label|default(website_name ~ ' — featured articles'))|e('html_attr') }}"
> >
{% do prefetch_article_card_covers(tiles) %}
{% for tile in tiles %} {% for tile in tiles %}
{% set _hue = (tile.categoryTitle|default('x')|length * 47) % 360 %} {% set _hue = (tile.categoryTitle|default('x')|length * 47) % 360 %}
{% set item = tile.article %} {% set item = tile.article %}

1
templates/components/Organisms/HomeMagazineArticleStrip.html.twig

@ -8,6 +8,7 @@
aria-label="{{ (website_name ~ ' — featured articles')|e('html_attr') }}" aria-label="{{ (website_name ~ ' — featured articles')|e('html_attr') }}"
> >
<div class="home-curation-landmark__articles"> <div class="home-curation-landmark__articles">
{% do prefetch_article_card_covers(tiles) %}
{% for tile in tiles %} {% for tile in tiles %}
{% set item = tile.article %} {% set item = tile.article %}
{% set article_href = (item.pubkey and npub_from_hex(item.pubkey) != '') ? path('article', { npub: npub_from_hex(item.pubkey), slug: item.slug }) : path('article-legacy-redirect', { slug: item.slug }) %} {% set article_href = (item.pubkey and npub_from_hex(item.pubkey) != '') ? path('article', { npub: npub_from_hex(item.pubkey), slug: item.slug }) : path('article-legacy-redirect', { slug: item.slug }) %}

1
tests/Service/ArticleBodyHighlightInjectorTest.php

@ -134,6 +134,7 @@ final class ArticleBodyHighlightInjectorTest extends TestCase
private function createInjector(): ArticleBodyHighlightInjector private function createInjector(): ArticleBodyHighlightInjector
{ {
$meta = $this->createMock(HighlightAuthorMetadataProvider::class); $meta = $this->createMock(HighlightAuthorMetadataProvider::class);
$meta->method('prefetchMetadataForNpubs');
$meta->method('getMetadata')->willReturn( $meta->method('getMetadata')->willReturn(
(object) [ (object) [
'display_name' => 'Test', 'display_name' => 'Test',

1
tests/Service/ArticleHighlightCommonMarkPipelineTest.php

@ -104,6 +104,7 @@ final class ArticleHighlightCommonMarkPipelineTest extends KernelTestCase
private function createInjector(): ArticleBodyHighlightInjector private function createInjector(): ArticleBodyHighlightInjector
{ {
$meta = $this->createMock(HighlightAuthorMetadataProvider::class); $meta = $this->createMock(HighlightAuthorMetadataProvider::class);
$meta->method('prefetchMetadataForNpubs');
$meta->method('getMetadata')->willReturn( $meta->method('getMetadata')->willReturn(
(object) ['display_name' => 'Test', 'name' => 'Test', 'picture' => ''], (object) ['display_name' => 'Test', 'name' => 'Test', 'picture' => ''],
); );

24
tests/Service/NostrWireEventMergeTest.php

@ -94,4 +94,28 @@ final class NostrWireEventMergeTest extends TestCase
$this->assertFalse($this->m->isNip33ParameterizedKind(29_999)); $this->assertFalse($this->m->isNip33ParameterizedKind(29_999));
$this->assertFalse($this->m->isNip33ParameterizedKind(40_000)); $this->assertFalse($this->m->isNip33ParameterizedKind(40_000));
} }
public function testMagazineEventToPublicationEntityAcceptsDecodedWireArray(): void
{
$pk = str_repeat('e', 64);
$id = str_repeat('f', 64);
$data = [
'kind' => 30_040,
'id' => $id,
'pubkey' => $pk,
'content' => 'summary',
'created_at' => 1_700_000_000,
'tags' => [['d', 'newsroom-magazine-on-imwald-by-laeserin-category-bitcoin']],
'sig' => str_repeat('a', 128),
];
$entity = $this->m->magazineEventToPublicationEntity($data);
$this->assertNotNull($entity);
$this->assertSame($id, $entity->getId());
$this->assertSame(30_040, $entity->getKind());
$this->assertSame($pk, $entity->getPubkey());
$this->assertSame('summary', $entity->getContent());
$this->assertSame(1_700_000_000, $entity->getCreatedAt());
$this->assertSame($data['tags'], $entity->getTags());
}
} }

Loading…
Cancel
Save