Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

module: rest-api #50

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"doctrine/doctrine-bundle": "^2.12",
"doctrine/doctrine-migrations-bundle": "^3.3",
"doctrine/orm": "^3.1",
"knplabs/php-json-schema-bundle": "^0.2.1",
"nelmio/api-doc-bundle": "^4.10",
"phpdocumentor/reflection-docblock": "^5.4",
"symfony/asset": "^7.0",
"symfony/console": "^7.0",
Expand Down
502 changes: 468 additions & 34 deletions composer.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
KnpLabs\JsonSchemaBundle\JsonSchemaBundle::class => ['all' => true],
Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
];
12 changes: 12 additions & 0 deletions config/packages/nelmio_api_doc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
nelmio_api_doc:
documentation:
servers:
- url: http://localhost/api
description: Local API
info:
title: Dinotaupia
description: This is a application about dinosaurs
version: 1.0.0
areas: # to filter documented areas
path_patterns:
- ^/api(?!/doc$) # Accepts routes under /api except /api/doc
9 changes: 9 additions & 0 deletions config/routes/nelmio_api_doc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
app.swagger:
path: /api/doc.json
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger }

app.swagger_ui:
path: /api/doc
methods: GET
defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
14 changes: 14 additions & 0 deletions config/serializer/dinosaur.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
App\Entity\Dinosaur:
attributes:
id:
groups: ['dinosaur', 'dinosaurs']
name:
groups: ['dinosaur', 'dinosaurs']
gender:
groups: ['dinosaur']
age:
groups: ['dinosaur']
eyesColor:
groups: ['dinosaur']
species:
groups: ['dinosaur']
102 changes: 102 additions & 0 deletions src/Controller/API/Dinosaurs/Create.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace App\Controller\API\Dinosaurs;

use App\Entity\Dinosaur;
use App\Entity\Species;
use App\Validation\JsonSchema\Object\Dinosaur\CreateSchema;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use KnpLabs\JsonSchema\Validator;
use KnpLabs\JsonSchemaBundle\Exception\JsonSchemaException;
use KnpLabs\JsonSchemaBundle\OpenApi\Attributes\JsonContent;
use KnpLabs\JsonSchemaBundle\RequestHandler;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

class Create extends AbstractController
{
public function __construct(
private readonly SerializerInterface $serializer,
private readonly Validator $validator,
private readonly RequestHandler $requestHandler
)
{
}

#[Route('/api/dinosaurs', methods: 'POST')]
#[OA\Tag('dinosaur')]
#[OA\RequestBody(
required: true,
content: new JsonContent(CreateSchema::class)
)]
#[OA\Response(
response: Response::HTTP_CREATED,
description: 'Create and return a dinosaur',
content: new Model(type: Dinosaur::class, groups: ['dinosaur'])
)]
#[OA\Response(
response: Response::HTTP_BAD_REQUEST,
description: 'Bad request'
)]
#[OA\Response(
response: Response::HTTP_UNPROCESSABLE_ENTITY,
description: 'The species ID does not exists'
)]
public function __invoke(ManagerRegistry $manager, Request $request): Response
{
try {
$dinosaurData = $this->requestHandler->extractJson($request, CreateSchema::class);
} catch (JsonSchemaException $e) {
return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST, json: true);
}

$species = $manager
->getRepository(Species::class)
->find($dinosaurData['speciesId'])
;

if (!$species instanceof Species) {
return new JsonResponse([
'message' => sprintf('Species with id %s not found', $dinosaurData['speciesId']),
Response::HTTP_UNPROCESSABLE_ENTITY
]);
}

try {
$dinosaur = new Dinosaur(
$dinosaurData['name'],
$dinosaurData['gender'],
$species,
$dinosaurData['age'],
$dinosaurData['eyesColor'],
);

$em = $manager->getManager();
$em->persist($dinosaur);
$em->flush();

$content = $this->serializer->serialize(
$dinosaur,
'json',
['groups' => ['dinosaur']]
);

return new JsonResponse($content, Response::HTTP_CREATED, json: true);
} catch (Exception $e) {
return new JsonResponse([
'message' => 'Something went wrong',
'error' => $e->getMessage(),
'stack' => $e->getTraceAsString(),
], Response::HTTP_BAD_REQUEST);
}
}
}
51 changes: 51 additions & 0 deletions src/Controller/API/Dinosaurs/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace App\Controller\API\Dinosaurs;

use App\Entity\Dinosaur;
use Doctrine\Persistence\ManagerRegistry;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class Delete extends AbstractController
{
#[Route('/api/dinosaurs/{id}', methods: 'DELETE')]
#[OA\Tag('dinosaur')]
#[OA\Parameter(
name: 'id',
description: 'ID of the dinosaur to delete',
type: 'string'
)]
#[OA\Response(
response: Response::HTTP_UNPROCESSABLE_ENTITY,
description: 'Dinosaur with given ID not found'
)]
#[OA\Response(
response: Response::HTTP_NO_CONTENT,
description: 'Dinosaur successfully deleted'
)]
public function __invoke(ManagerRegistry $manager, string $id): Response
{
$dinosaur = $manager
->getRepository(Dinosaur::class)
->find($id)
;

if (!$dinosaur instanceof Dinosaur) {
return new JsonResponse([
'message' => sprintf('Dinosaur with id %s not found', $id)
], Response::HTTP_UNPROCESSABLE_ENTITY);
}

$em = $manager->getManager();
$em->remove($dinosaur);
$em->flush();

return new Response(status: Response::HTTP_NO_CONTENT);
}
}
98 changes: 98 additions & 0 deletions src/Controller/API/Dinosaurs/GetAll.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php

declare(strict_types=1);

namespace App\Controller\API\Dinosaurs;

use App\Entity\Dinosaur;
use App\Repository\DinosaurRepository;
use Doctrine\Persistence\ManagerRegistry;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

class GetAll extends AbstractController
{
public function __construct(
private readonly SerializerInterface $serializer,
private readonly DinosaurRepository $dinosaurs
)
{
}

#[Route('/api/dinosaurs', methods: 'GET')]
#[OA\Tag('dinosaur')]
#[OA\Parameter(
parameter: 'page',
name: 'page',
schema: new OA\Schema(
type: 'integer',
default: 0,
minimum: 0
)
)]
#[OA\Parameter(
parameter: 'limit',
name: 'limit',
in: 'query',
schema: new OA\Schema(
type: 'integer',
default: 25,
minimum: 1
),
)]
#[OA\Parameter(
parameter: 'filters',
name: 'filters',
in: 'query',
schema: new OA\Schema(type: 'object'),
example: '{"gender": "Male"}'
)]
#[OA\Parameter(
parameter: 'sorts',
name: 'sorts',
in: 'query',
schema: new OA\Schema(type: 'object'),
example: '{"age": "ASC"}'
)]
#[OA\Response(
response: Response::HTTP_OK,
description: 'List all the dinosaurs',
content: new OA\JsonContent(
type: 'array',
items: new OA\Items(
ref: new Model(
type: Dinosaur::class,
groups: ['dinosaurs']
)
)
)
)]
public function __invoke(Request $request): Response
{
$dinosaurs = $this->dinosaurs
->search(
filters: json_decode($request->query->get('filters')),
sorts: json_decode($request->query->get('sorts')),
page: $request->query->get('page'),
limit: $request->query->get('limit')
)
;

$content = $this->serializer->serialize(
$dinosaurs,
'json',
['groups' => ['dinosaurs']]
);

return new JsonResponse(
$content,
json: true
);
}
}
57 changes: 57 additions & 0 deletions src/Controller/API/Dinosaurs/GetOne.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace App\Controller\API\Dinosaurs;

use App\Entity\Dinosaur;
use Doctrine\Persistence\ManagerRegistry;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Attributes as OA;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Serializer\SerializerInterface;

class GetOne extends AbstractController
{
public function __construct(
private readonly SerializerInterface $serializer
)
{
}

#[Route('/api/dinosaurs/{id}', methods: 'GET')]
#[OA\Tag('dinosaur')]
#[OA\Response(
response: Response::HTTP_UNPROCESSABLE_ENTITY,
description: 'Dinosaur with given ID not found'
)]
#[OA\Response(
response: Response::HTTP_OK,
description: 'Specified dinosaur',
content: new Model(type: Dinosaur::class, groups: ['dinosaur'])
)]
public function __invoke(ManagerRegistry $manager, string $id): Response
{
$dinosaur = $manager
->getRepository(Dinosaur::class)
->find($id)
;

if (!$dinosaur instanceof Dinosaur) {
return new JsonResponse([
'message' => sprintf('Dinosaur with id %s not found.', $id)
], Response::HTTP_UNPROCESSABLE_ENTITY);
}

$content = $this->serializer->serialize(
$dinosaur,
'json',
['groups' => ['dinosaur']]
);

return new JsonResponse($content, json: true);
}
}
21 changes: 21 additions & 0 deletions src/Controller/API/Hello.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace App\Controller\API;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class Hello extends AbstractController
{
#[Route('/api/hello', methods: 'GET')]
public function __invoke(): Response
{
return new JsonResponse([
'message' => 'Hello world!'
]);
}
}
Loading