Add endpoint for getting a feed via it's URL, as well as caching the result

This commit is contained in:
Lewis Dale 2024-09-20 14:46:20 +01:00
parent 70e78bcf42
commit 78ed53d529
5 changed files with 55 additions and 31 deletions

View File

@ -26,6 +26,22 @@ class FeedController
return $this->view->render($response, 'index.twig.html', ['feeds' => $feeds]); return $this->view->render($response, 'index.twig.html', ['feeds' => $feeds]);
} }
public function get_feed(ServerRequestInterface $request, ResponseInterface $response)
{
$this->logger->info("FeedController::get_feed() called");
$feed = $this->feedRepository->find($request->getAttribute('id'));
$filtered = $feed->get_filtered_feed();
$body = $response->getBody();
$body->write($filtered->asXML());
$this->feedRepository->save($feed);
return $response->withHeader('Content-Type', 'application/rss+xml')
->withBody($body)
->withStatus(200);
}
public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface public function create(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
{ {
$this->logger->info("FeedController::create() called"); $this->logger->info("FeedController::create() called");

View File

@ -45,10 +45,15 @@ class Feed
#[ORM\Column(type: XMLElement::NAME, nullable: true)] #[ORM\Column(type: XMLElement::NAME, nullable: true)]
public SimpleXMLElement|null $filteredFeed; public SimpleXMLElement|null $filteredFeed;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: true)]
public \DateTimeInterface|null $lastFetched;
public function fetch(): void public function fetch(): void
{ {
if (Robots::allowed($this->url)) { if (Robots::allowed($this->url)) {
$this->remoteFeed = simplexml_load_file($this->url); $this->remoteFeed = simplexml_load_file($this->url);
$this->filter();
$this->lastFetched = new \DateTimeImmutable();
} else { } else {
throw new \Exception("Robots.txt disallows fetching this feed"); throw new \Exception("Robots.txt disallows fetching this feed");
} }
@ -65,10 +70,6 @@ class Feed
public function filter() : void public function filter() : void
{ {
if ($this->remoteFeed === null) {
$this->fetch();
}
$dom = dom_import_simplexml($this->remoteFeed); $dom = dom_import_simplexml($this->remoteFeed);
$doc = $dom->ownerDocument; $doc = $dom->ownerDocument;
@ -78,11 +79,27 @@ class Feed
$xpath_query = '//item[' . $filter_queries . ']'; $xpath_query = '//item[' . $filter_queries . ']';
$to_remove = $xpath->query($xpath_query); $to_remove = $xpath->query($xpath_query);
echo "Removing " . $to_remove->length . " items\n";
foreach ($to_remove as $item) { foreach ($to_remove as $item) {
$item->parentNode->removeChild($item); $item->parentNode->removeChild($item);
} }
$title = $xpath->query('//channel/title');
$title->item(0)->nodeValue = $this->title;
$this->filteredFeed = simplexml_import_dom($dom); $this->filteredFeed = simplexml_import_dom($dom);
} }
public function get_filtered_feed(): SimpleXMLElement
{
if (!$this->lastFetched || $this->lastFetched < new \DateTimeImmutable('-1 hour')) {
$this->fetch();
}
if ($this->filteredFeed === null) {
$this->filter();
}
return $this->filteredFeed;
}
} }

View File

@ -15,4 +15,10 @@ class FeedRepository extends EntityRepository
{ {
parent::__construct($em, $em->getClassMetadata(Feed::class)); parent::__construct($em, $em->getClassMetadata(Feed::class));
} }
public function save(Feed $feed): void
{
$this->_em->persist($feed);
$this->_em->flush();
}
} }

View File

@ -44,36 +44,19 @@ class TestFeed extends Command
$url = $input->getArgument("url"); $url = $input->getArgument("url");
$title = $input->getArgument("title"); $title = $input->getArgument("title");
// $output->writeln("Setting up a new feed for: $url"); $output->writeln("Setting up a new feed for: $url");
//
// $feed = new Feed();
// $feed->url = $url;
// $feed->title = $title;
// $feed->fetch();
// $feed->feedFilters = new ArrayCollection();
// $feed->feedFilters->add( new FeedFilter(FilterTarget::TITLE, FilterType::INCLUDE, "[No Ads]", $feed));
//
// $this->em->persist($feed);
// $this->em->flush();
//
// $cnt = $this->feedRepository->count([]);
// $output->writeln("Feed count: $cnt");
$saved_feed = $this->feedRepository->findOneBy(['url' => $url, 'title' => $title]); $feed = new Feed();
$output->writeln("Feed title: " . $saved_feed->title); $feed->url = $url;
$feed->title = $title;
$s2_filter = new FeedFilter(FilterTarget::TITLE, FilterType::EXCLUDE, "S2", $saved_feed); $feed->feedFilters = new ArrayCollection();
$this->em->persist($s2_filter); $feed->feedFilters->add( new FeedFilter(FilterTarget::TITLE, FilterType::INCLUDE, "[No Ads]", $feed));
$feed->feedFilters->add( new FeedFilter(FilterTarget::TITLE, FilterType::EXCLUDE, "S2", $feed));
$this->em->persist($feed);
$this->em->flush(); $this->em->flush();
$saved_feed = $this->feedRepository->findOneBy(['url' => $url, 'title' => $title]);
$saved_feed->filter();
$this->em->persist($saved_feed);
$this->em->flush();
$output->writeln("Filtered feed: " . $saved_feed->filteredFeed->asXML('feed_filtered.xml'));
return Command::SUCCESS; return Command::SUCCESS;
} }
} }

View File

@ -23,6 +23,8 @@ $app->get("/", [SampleController::class, 'get']);
$app->get('/feed', [FeedController::class, 'get']); $app->get('/feed', [FeedController::class, 'get']);
$app->get('/feed/{id}', [FeedController::class, 'get_feed']);
$app->addErrorMiddleware(true, true, true); $app->addErrorMiddleware(true, true, true);
$app->run(); $app->run();