implement new episode route in the API

This commit is contained in:
dece 2024-09-01 16:17:39 +02:00
parent e97bbc1bc1
commit a1ba12a938
7 changed files with 125 additions and 1 deletions

View file

@ -26,6 +26,7 @@
"symfony/http-client": "7.1.*", "symfony/http-client": "7.1.*",
"symfony/intl": "7.1.*", "symfony/intl": "7.1.*",
"symfony/mailer": "7.1.*", "symfony/mailer": "7.1.*",
"symfony/messenger": "7.1.*",
"symfony/mime": "7.1.*", "symfony/mime": "7.1.*",
"symfony/monolog-bundle": "^3.0", "symfony/monolog-bundle": "^3.0",
"symfony/notifier": "7.1.*", "symfony/notifier": "7.1.*",

2
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "856f163b5bcd6da7dc6bc4b1953eb4a0", "content-hash": "7889f2e58c2d416ef961044023f570be",
"packages": [ "packages": [
{ {
"name": "doctrine/annotations", "name": "doctrine/annotations",

View file

@ -22,6 +22,9 @@ security:
logout: logout:
path: app_logout path: app_logout
target: app_index target: app_index
http_basic:
realm: Secured Area
entry_point: form_login
role_hierarchy: role_hierarchy:
ROLE_ADMIN: ROLE_USER ROLE_ADMIN: ROLE_USER
access_control: access_control:

View file

@ -0,0 +1,64 @@
<?php
namespace App\Controller;
use App\Message\DownloadRequest;
use App\Repository\PodcastRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Component\Serializer\SerializerInterface;
#[Route('/api')]
class ApiController extends AbstractController
{
#[Route('/podcasts', name: 'app_api_list_podcasts')]
public function listPodcasts(PodcastRepository $podcastRepo): JsonResponse
{
$data = array_map(fn ($p) => $p->getSlug(), $podcastRepo->findAll());
return new JsonResponse(['slugs' => $data]);
}
#[Route('/podcasts/{slug}', name: 'app_api_show_podcast')]
public function getPodcast(string $slug, PodcastRepository $podcastRepo, SerializerInterface $serializer): JsonResponse
{
$podcast = $podcastRepo->findOneBy(['slug' => $slug]);
if (null === $podcast) {
return new JsonResponse(['error' => 'No podcast with that slug.'], 404);
}
return (new JsonResponse())->setContent($serializer->serialize($podcast, 'json'));
}
#[IsGranted('ROLE_USER')]
#[Route('/podcasts/{slug}/add', name: 'app_api_add_podcast_episode', methods: ['POST'])]
public function addPodcastEpisode(
string $slug,
Request $request,
PodcastRepository $podcastRepo,
MessageBusInterface $bus,
): JsonResponse {
$podcast = $podcastRepo->findOneBy(['slug' => $slug]);
if (null === $podcast) {
return new JsonResponse(['error' => 'No podcast with that slug.'], 404);
}
if ($podcast->getOwner() !== $this->getUser()) {
return new JsonResponse(['error' => 'Only the podcast owner can add an episode.'], 403);
}
$payload = $request->getPayload();
$url = $payload->getString('url');
if (empty($url)) {
return new JsonResponse(['error' => '"url" is a required parameter.'], 400);
}
$description = $payload->getString('description');
$bus->dispatch(new DownloadRequest($url, $slug, $description ?: null));
return new JsonResponse(['message' => 'Request dispatched.']);
}
}

View file

@ -9,6 +9,7 @@ use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Serializer\Attribute\Ignore;
use Symfony\Component\String\Slugger\SluggerInterface; use Symfony\Component\String\Slugger\SluggerInterface;
#[ORM\Entity(repositoryClass: PodcastRepository::class)] #[ORM\Entity(repositoryClass: PodcastRepository::class)]
@ -26,6 +27,7 @@ class Podcast
#[ORM\Column(length: 255, unique: true)] #[ORM\Column(length: 255, unique: true)]
private string $slug; private string $slug;
#[Ignore]
#[ORM\OneToMany(mappedBy: 'podcast', targetEntity: Episode::class)] #[ORM\OneToMany(mappedBy: 'podcast', targetEntity: Episode::class)]
private Collection $episodes; private Collection $episodes;
@ -44,6 +46,7 @@ class Podcast
#[ORM\Column(length: 255, nullable: true)] #[ORM\Column(length: 255, nullable: true)]
private ?string $logoFilename; private ?string $logoFilename;
#[Ignore]
#[ORM\ManyToOne(inversedBy: 'podcasts')] #[ORM\ManyToOne(inversedBy: 'podcasts')]
#[ORM\JoinColumn(nullable: true)] #[ORM\JoinColumn(nullable: true)]
private ?User $owner = null; private ?User $owner = null;

View file

@ -0,0 +1,13 @@
<?php
namespace App\Message;
class DownloadRequest
{
public function __construct(
public readonly string $url,
public readonly string $podcastSlug,
public readonly ?string $description = null,
) {
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace App\MessageHandler;
use App\Message\DownloadRequest;
use App\Repository\PodcastRepository;
use App\Service\DownloadService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class DownloadRequestHandler
{
public function __construct(
protected LoggerInterface $logger,
protected EntityManagerInterface $em,
protected DownloadService $service,
protected PodcastRepository $podcastRepo,
) {
}
public function __invoke(DownloadRequest $request): void
{
$this->logger->info('Processing download.', ['url' => $request->url]);
$episode = $this->service->download($request->url);
if ($episode === false) {
$this->logger->error('Episode download failed.');
return;
}
$episode->setPodcast($this->podcastRepo->findOneBy(['slug' => $request->podcastSlug]));
if ($request->description) {
$episode->setDescription($request->description);
}
$this->em->persist($episode);
$this->em->flush();
}
}