Migration to last.fm, removed old spotify api client. FOSRestBundle API serving data driven front end (JWT).
This commit is contained in:
1 parent
391ecf1732
commit
56dea097d6
51 files changed
+1749
-844
No files matched your search
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Api\V1;
|
||||
|
||||
use App\Entity\Album;
|
||||
use App\Entity\Review;
|
||||
use App\Entity\User;
|
||||
use App\Repository\AlbumRepository;
|
||||
use App\Repository\ReviewRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use FOS\RestBundle\Controller\AbstractFOSRestController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
#[Route('/api/v1/reviews')]
|
||||
class ReviewApiController extends AbstractFOSRestController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ReviewRepository $reviewRepository,
|
||||
private readonly AlbumRepository $albumRepository,
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Route('', name: 'api_v1_review_index', methods: ['GET'])]
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$limit = max(1, min(100, (int) $request->query->get('limit', 50)));
|
||||
$reviews = $this->reviewRepository->findLatest($limit);
|
||||
|
||||
return $this->handleView($this->view([
|
||||
'data' => array_map(fn(Review $review) => $this->serializeReview($review), $reviews),
|
||||
], Response::HTTP_OK));
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'api_v1_review_show', requirements: ['id' => '\\d+'], methods: ['GET'])]
|
||||
public function show(Review $review): Response
|
||||
{
|
||||
return $this->handleView($this->view(['data' => $this->serializeReview($review)], Response::HTTP_OK));
|
||||
}
|
||||
|
||||
#[Route('', name: 'api_v1_review_create', methods: ['POST'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
$payload = $this->readJsonPayload($request);
|
||||
if ($payload === null) {
|
||||
return $this->handleView($this->view(['error' => 'invalid_json'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
|
||||
$albumId = trim((string) ($payload['album_id'] ?? ''));
|
||||
$album = $this->resolveAlbum($albumId);
|
||||
if (!$album instanceof Album) {
|
||||
return $this->handleView($this->view(['error' => 'album_not_found'], Response::HTTP_NOT_FOUND));
|
||||
}
|
||||
|
||||
$title = trim((string) ($payload['title'] ?? ''));
|
||||
$content = trim((string) ($payload['content'] ?? ''));
|
||||
$rating = (int) ($payload['rating'] ?? 0);
|
||||
|
||||
if ($title === '' || $content === '' || $rating < 1 || $rating > 10) {
|
||||
return $this->handleView($this->view(['error' => 'title_content_rating_required'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
|
||||
if (mb_strlen($content) < 20) {
|
||||
return $this->handleView($this->view(['error' => 'content_too_short'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
|
||||
$author = $this->getUser();
|
||||
if (!$author instanceof User) {
|
||||
return $this->handleView($this->view(['error' => 'authentication_required'], Response::HTTP_UNAUTHORIZED));
|
||||
}
|
||||
|
||||
$review = new Review();
|
||||
$review->setAlbum($album);
|
||||
$review->setAuthor($author);
|
||||
$review->setTitle($title);
|
||||
$review->setContent($content);
|
||||
$review->setRating($rating);
|
||||
|
||||
$this->entityManager->persist($review);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->handleView($this->view(['data' => $this->serializeReview($review)], Response::HTTP_CREATED));
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'api_v1_review_update', requirements: ['id' => '\\d+'], methods: ['PUT', 'PATCH'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function update(Review $review, Request $request): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('REVIEW_EDIT', $review);
|
||||
|
||||
$payload = $this->readJsonPayload($request);
|
||||
if ($payload === null) {
|
||||
return $this->handleView($this->view(['error' => 'invalid_json'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
|
||||
if (array_key_exists('title', $payload)) {
|
||||
$title = trim((string) $payload['title']);
|
||||
if ($title === '') {
|
||||
return $this->handleView($this->view(['error' => 'title_cannot_be_empty'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
$review->setTitle($title);
|
||||
}
|
||||
|
||||
if (array_key_exists('content', $payload)) {
|
||||
$content = trim((string) $payload['content']);
|
||||
if ($content === '') {
|
||||
return $this->handleView($this->view(['error' => 'content_cannot_be_empty'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
if (mb_strlen($content) < 20) {
|
||||
return $this->handleView($this->view(['error' => 'content_too_short'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
$review->setContent($content);
|
||||
}
|
||||
|
||||
if (array_key_exists('rating', $payload)) {
|
||||
$rating = (int) $payload['rating'];
|
||||
if ($rating < 1 || $rating > 10) {
|
||||
return $this->handleView($this->view(['error' => 'rating_out_of_range'], Response::HTTP_BAD_REQUEST));
|
||||
}
|
||||
$review->setRating($rating);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->handleView($this->view(['data' => $this->serializeReview($review)], Response::HTTP_OK));
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'api_v1_review_delete', requirements: ['id' => '\\d+'], methods: ['DELETE'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function delete(Review $review): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('REVIEW_DELETE', $review);
|
||||
|
||||
$this->entityManager->remove($review);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $this->handleView($this->view(null, Response::HTTP_NO_CONTENT));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function serializeReview(Review $review): array
|
||||
{
|
||||
$album = $review->getAlbum();
|
||||
|
||||
return [
|
||||
'id' => $review->getId(),
|
||||
'title' => $review->getTitle(),
|
||||
'content' => $review->getContent(),
|
||||
'rating' => $review->getRating(),
|
||||
'created_at' => $review->getCreatedAt()?->format(DATE_ATOM),
|
||||
'updated_at' => $review->getUpdatedAt()?->format(DATE_ATOM),
|
||||
'author' => [
|
||||
'id' => $review->getAuthor()?->getId(),
|
||||
'email' => $review->getAuthor()?->getEmail(),
|
||||
'display_name' => $review->getAuthor()?->getDisplayName(),
|
||||
],
|
||||
'album' => [
|
||||
'id' => $album?->getSource() === 'user' ? $album?->getLocalId() : $album?->getExternalId(),
|
||||
'name' => $album?->getName(),
|
||||
'source' => $album?->getSource(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveAlbum(string $albumId): ?Album
|
||||
{
|
||||
if ($albumId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$local = $this->albumRepository->findOneByLocalId($albumId);
|
||||
if ($local instanceof Album) {
|
||||
return $local;
|
||||
}
|
||||
|
||||
return $this->albumRepository->findOneByExternalId($albumId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>|null
|
||||
*/
|
||||
private function readJsonPayload(Request $request): ?array
|
||||
{
|
||||
$content = trim($request->getContent());
|
||||
if ($content === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$decoded = $request->toArray();
|
||||
} catch (\JsonException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return is_array($decoded) ? $decoded : null;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user