diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index 310aa2e..f56a06d 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -61,30 +61,6 @@ class DefaultController extends AbstractController return $this->render('pages/lists.html.twig'); } - /** - * @throws InvalidArgumentException - */ - #[Route('/latest', name: 'latest')] - public function latest() : Response - { - $cacheKey = 'home-latest-articles'; - $latest = $this->redisCache->get($cacheKey, function (ItemInterface $item) { - $item->expiresAfter(13600); // about 4 hours - // get latest articles - $q = new Query(); - $q->setSize(12); - $q->setSort(['createdAt' => ['order' => 'desc']]); - $col = new Collapse(); - $col->setFieldname('pubkey'); - $q->setCollapse($col); - return $this->finder->find($q); - }); - - return $this->render('pages/latest.html.twig', [ - 'latest' => $latest - ]); - } - /** * Magazine front page: title, summary, category links, featured list. * @throws InvalidArgumentException @@ -286,17 +262,6 @@ class DefaultController extends AbstractController ]); } - /** - * @throws InvalidArgumentException - */ - #[Route('/list/{slug}', name: 'reading-list')] - public function readingList($slug, CacheInterface $redisCache, - FinderInterface $finder, - LoggerInterface $logger): Response - { - return new Response('Not implemented yet', 501); - } - /** * OG Preview endpoint for URLs diff --git a/src/Controller/ReadingListController.php b/src/Controller/ReadingListController.php index 77084ba..bead369 100644 --- a/src/Controller/ReadingListController.php +++ b/src/Controller/ReadingListController.php @@ -5,11 +5,19 @@ declare(strict_types=1); namespace App\Controller; use App\Entity\Event; +use App\Enum\KindsEnum; use Doctrine\ORM\EntityManagerInterface; +use Elastica\Query; +use Elastica\Query\BoolQuery; +use Elastica\Query\Term; +use FOS\ElasticaBundle\Finder\FinderInterface; +use Psr\Cache\InvalidArgumentException; +use Psr\Log\LoggerInterface; use swentel\nostr\Key\Key; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Contracts\Cache\CacheInterface; class ReadingListController extends AbstractController { @@ -19,7 +27,7 @@ class ReadingListController extends AbstractController $lists = []; $user = $this->getUser(); $pubkeyHex = null; - if ($user && method_exists($user, 'getUserIdentifier')) { + if ($user) { try { $key = new Key(); $pubkeyHex = $key->convertToHex($user->getUserIdentifier()); @@ -59,6 +67,7 @@ class ReadingListController extends AbstractController 'summary' => $summary, 'slug' => $slug, 'createdAt' => $ev->getCreatedAt(), + 'pubkey' => $ev->getPubkey(), ]; } } @@ -74,4 +83,68 @@ class ReadingListController extends AbstractController { return $this->render('reading_list/compose.html.twig'); } + + /** + * + * @throws InvalidArgumentException + */ + #[Route('/p/{pubkey}/list/{slug}', name: 'reading-list')] + public function readingList($pubkey, $slug, CacheInterface $redisCache, + EntityManagerInterface $em, + FinderInterface $finder, + LoggerInterface $logger): Response + { + $key = 'single-reading-list-' . $pubkey . '-' . $slug; + $logger->info(sprintf('Reading list: %s', $key)); + $list = $redisCache->get($key, function() use ($em, $pubkey, $slug) { + // find reading list by pubkey+slug, kind 30040 + $lists = $em->getRepository(Event::class)->findBy(['pubkey' => $pubkey, 'kind' => KindsEnum::PUBLICATION_INDEX]); + // filter by tag d = $slug + $lists = array_filter($lists, function($ev) use ($slug) { + return $ev->getSlug() === $slug; + }); + // sort revisions and keep latest + usort($lists, function($a, $b) { + return $b->getCreatedAt() <=> $a->getCreatedAt(); + }); + return array_pop($lists); + }); + + // fetch articles listed in the list's a tags + $coordinates = []; // Store full coordinates (kind:author:slug) + // Extract category metadata and article coordinates + foreach ($list->getTags() as $tag) { + if ($tag[0] === 'a') { + $coordinates[] = $tag[1]; // Store the full coordinate + } + } + $articles = []; + if (count($coordinates) > 0) { + $boolQuery = new BoolQuery(); + foreach ($coordinates as $coord) { + $parts = explode(':', $coord, 3); + [$kind, $author, $slug] = $parts; + $termQuery = new BoolQuery(); + $termQuery->addMust(new Term(['kind' => (int)$kind])); + $termQuery->addMust(new Term(['pubkey' => strtolower($author)])); + $termQuery->addMust(new Term(['slug' => $slug])); + $boolQuery->addShould($termQuery); + } + $finalQuery = new Query($boolQuery); + $finalQuery->setSize(100); // Limit to 100 results + $results = $finder->find($finalQuery); + // Index results by their full coordinate for easy lookup + foreach ($results as $result) { + if ($result instanceof Event) { + $coordKey = sprintf('%d:%s:%s', $result->getKind(), strtolower($result->getPubkey()), $result->getSlug()); + $articles[$coordKey] = $result; + } + } + } + + return $this->render('pages/list.html.twig', [ + 'list' => $list, + 'articles' => $articles, + ]); + } } diff --git a/templates/components/Organisms/MagazineHero.html.twig b/templates/components/Organisms/MagazineHero.html.twig index 23bbddd..a1f5d38 100644 --- a/templates/components/Organisms/MagazineHero.html.twig +++ b/templates/components/Organisms/MagazineHero.html.twig @@ -1,11 +1,13 @@
{{ magazine.summary }}
{% endif %} - +by
{{ item.summary }}
+ {% endif %} +No reading lists yet.
{% endif %}off the presses
-{{ list.summary }}
+for collections, curations, courses and more
diff --git a/templates/pages/newsstand.html.twig b/templates/pages/newsstand.html.twig index a722397..8be9c0e 100644 --- a/templates/pages/newsstand.html.twig +++ b/templates/pages/newsstand.html.twig @@ -20,6 +20,6 @@ {% endblock %} {% block aside %} -