From 6594c9688e356f8ceae6c4c1fa6f3b2c4c065c0f Mon Sep 17 00:00:00 2001 From: Lucas Pirola Date: Tue, 18 Jun 2024 08:14:53 -0300 Subject: [PATCH] =?UTF-8?q?Adiciona=20vers=C3=A3o=20mais=20recente=20mapac?= =?UTF-8?q?ultural=20ce?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/mapas/app/.env.example | 1 + api/mapas/app/.gitignore | 3 +- api/mapas/app/README.md | 34 +++- api/mapas/app/bin/console | 11 +- api/mapas/app/config/di.php | 70 +++++++ api/mapas/app/routes/api/agent.php | 2 +- api/mapas/app/routes/api/event.php | 2 +- api/mapas/app/routes/api/opportunity.php | 2 +- api/mapas/app/routes/api/project.php | 2 +- api/mapas/app/routes/api/seal.php | 2 +- api/mapas/app/routes/api/space.php | 2 +- api/mapas/app/routes/api/term.php | 3 + api/mapas/app/src/Application/Environment.php | 36 ++++ api/mapas/app/src/Command/FixturesCommand.php | 12 +- api/mapas/app/src/Command/TestsCommand.php | 3 +- .../Controller/Api/AbstractApiController.php | 19 ++ .../src/Controller/Api/AgentApiController.php | 99 ++++------ .../src/Controller/Api/EventApiController.php | 88 ++++----- .../Api/OpportunityApiController.php | 79 +++----- .../Controller/Api/ProjectApiController.php | 82 +++----- .../src/Controller/Api/SealApiController.php | 71 ++----- .../src/Controller/Api/SpaceApiController.php | 98 +++++----- .../src/Controller/Api/TermApiController.php | 45 ++++- api/mapas/app/src/DTO/TermDto.php | 27 +++ api/mapas/app/src/Entity/Term.php | 65 +++++++ api/mapas/app/src/Kernel.php | 34 +++- .../app/src/Repository/AgentRepository.php | 21 +- .../app/src/Repository/EventRepository.php | 26 ++- .../Interface/AgentRepositoryInterface.php | 18 ++ .../Interface/EventRepositoryInterface.php | 22 +++ .../OpportunityRepositoryInterface.php | 20 ++ .../Interface/ProjectRepositoryInterface.php | 18 ++ .../Interface/SealRepositoryInterface.php | 18 ++ .../Interface/SpaceRepositoryInterface.php | 18 ++ .../Interface/TermRepositoryInterface.php | 18 ++ .../src/Repository/OpportunityRepository.php | 26 ++- .../app/src/Repository/ProjectRepository.php | 21 +- .../app/src/Repository/SealRepository.php | 25 ++- .../app/src/Repository/SpaceRepository.php | 21 +- .../app/src/Repository/TermRepository.php | 22 ++- api/mapas/app/src/Request/AgentRequest.php | 25 +-- api/mapas/app/src/Request/EventRequest.php | 41 ++-- .../app/src/Request/OpportunityRequest.php | 35 +--- api/mapas/app/src/Request/ProjectRequest.php | 14 +- api/mapas/app/src/Request/SealRequest.php | 26 +-- api/mapas/app/src/Request/SpaceRequest.php | 19 +- api/mapas/app/src/Request/TermRequest.php | 53 +++++ api/mapas/app/src/Service/AgentService.php | 40 +--- api/mapas/app/src/Service/EventService.php | 35 ++-- .../Interface/AgentServiceInterface.php | 18 ++ .../Interface/EventServiceInterface.php | 18 ++ .../Interface/OpportunityServiceInterface.php | 18 ++ .../Interface/ProjectServiceInterface.php | 16 ++ .../Interface/SealServiceInterface.php | 16 ++ .../Interface/SpaceServiceInterface.php | 18 ++ .../Interface/TermServiceInterface.php | 16 ++ .../app/src/Service/OpportunityService.php | 44 ++--- api/mapas/app/src/Service/ProjectService.php | 50 +++-- api/mapas/app/src/Service/SealService.php | 51 ++--- api/mapas/app/src/Service/SpaceService.php | 40 ++-- api/mapas/app/src/Service/TermService.php | 48 +++++ .../Validator/Constraints/UniqueInEntity.php | 27 +++ .../src/Validator/UniqueInEntityValidator.php | 42 ++++ .../Functional/AgentApiControllerTest.php | 120 ++++++++++++ .../Functional/EventApiControllerTest.php | 107 ++++++++++ .../OpportunityApiControllerTest.php | 32 +++ .../Functional/ProjectApiControllerTest.php | 96 +++++++++ .../Functional/SealApiControllerTest.php | 100 ++++++++++ .../Functional/SpaceApiControllerTest.php | 99 ++++++++++ .../Functional/TermApiControllerTest.php | 183 ++++++++++++++++++ .../Functional/WelcomeApiControllerTest.php | 44 +++++ .../app/tests/fixtures/TermTestFixtures.php | 22 +++ 72 files changed, 2076 insertions(+), 623 deletions(-) create mode 100644 api/mapas/app/.env.example create mode 100644 api/mapas/app/config/di.php create mode 100644 api/mapas/app/src/Application/Environment.php create mode 100644 api/mapas/app/src/Controller/Api/AbstractApiController.php create mode 100644 api/mapas/app/src/DTO/TermDto.php create mode 100644 api/mapas/app/src/Entity/Term.php create mode 100644 api/mapas/app/src/Repository/Interface/AgentRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/EventRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/OpportunityRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/ProjectRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/SealRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/SpaceRepositoryInterface.php create mode 100644 api/mapas/app/src/Repository/Interface/TermRepositoryInterface.php create mode 100644 api/mapas/app/src/Request/TermRequest.php create mode 100644 api/mapas/app/src/Service/Interface/AgentServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/EventServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/OpportunityServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/ProjectServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/SealServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/SpaceServiceInterface.php create mode 100644 api/mapas/app/src/Service/Interface/TermServiceInterface.php create mode 100644 api/mapas/app/src/Service/TermService.php create mode 100644 api/mapas/app/src/Validator/Constraints/UniqueInEntity.php create mode 100644 api/mapas/app/src/Validator/UniqueInEntityValidator.php create mode 100644 api/mapas/app/tests/Functional/AgentApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/EventApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/OpportunityApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/ProjectApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/SealApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/SpaceApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/TermApiControllerTest.php create mode 100644 api/mapas/app/tests/Functional/WelcomeApiControllerTest.php create mode 100644 api/mapas/app/tests/fixtures/TermTestFixtures.php diff --git a/api/mapas/app/.env.example b/api/mapas/app/.env.example new file mode 100644 index 0000000000..14bde28f4a --- /dev/null +++ b/api/mapas/app/.env.example @@ -0,0 +1 @@ +APP_ENV= \ No newline at end of file diff --git a/api/mapas/app/.gitignore b/api/mapas/app/.gitignore index a725465aee..b44d4ebbd0 100644 --- a/api/mapas/app/.gitignore +++ b/api/mapas/app/.gitignore @@ -1 +1,2 @@ -vendor/ \ No newline at end of file +vendor/ +.env \ No newline at end of file diff --git a/api/mapas/app/README.md b/api/mapas/app/README.md index ad91a2d668..e195dc5b03 100644 --- a/api/mapas/app/README.md +++ b/api/mapas/app/README.md @@ -388,7 +388,7 @@ Para criar um no cenário de teste funcional, basta adicionar sua nova classe no ```php Console Comma --- +## DI (Injeção de dependência) +Com a injeção de dependência diminuímos o acoplamento das nossas classes. E a razão para isso ser tão importante está no princípio da inversão de dependência em que o código deve depender de abstrações e não de implementações concretas. + +Documentação PHP-DI: https://php-di.org + +Todo o código se encontra no diretório `/app/config`, no arquivo `di.php`. + ## Console Commands @@ -451,11 +458,36 @@ php app/bin/console app:code-style :memo: Fixtures são dados falsos com a finalidade de testes. +#### Configuração do ambiente + +Antes de executar os fixtures, é necessário criar um arquivo `.env` dentro da pasta app (/app/.env). Este arquivo deve conter a configuração de ambiente necessária. Um arquivo de exemplo chamado `.env.example` foi fornecido para facilitar esse processo. + +1. Copie o arquivo `.env.example` para `.env`: + + ```sh + cp .env.example .env + ``` + +2. Abra o arquivo `.env` e configure as variáveis de ambiente. Para fins de desenvolvimento, você pode definir a variável `APP_ENV` como `local`: + + ```sh + APP_ENV=local + ``` + + +#### Executando os Fixtures + Para executar o conjunto de fixtures basta entrar no container da aplicação e executar ``` php app/bin/console app:fixtures ``` +> **Observação:** +> Se o arquivo `.env` não for encontrado, você verá a seguinte mensagem de erro: +> +> ```sh +> Please create a .env file in the root directory (/app/.env) +> ```
diff --git a/api/mapas/app/bin/console b/api/mapas/app/bin/console index d05eae7193..868ac156aa 100644 --- a/api/mapas/app/bin/console +++ b/api/mapas/app/bin/console @@ -4,12 +4,18 @@ require dirname(__DIR__, 2).'/vendor/autoload.php'; require dirname(__DIR__, 2).'/public/bootstrap.php'; +use App\Application\Environment; use App\Command\CodeStyleCommand; use App\Command\DebugRouterCommand; use App\Command\FixturesCommand; use App\Command\TestsCommand; use App\Command\WelcomeCommand; use Symfony\Component\Console\Application; +use Symfony\Component\Dotenv\Dotenv; + +$env = Environment::getEnvData(); +$dotenv = new Dotenv(); +$dotenv->load($env); $application = new Application(); $entityManager = $app->em ?? null; @@ -18,8 +24,11 @@ $application->addCommands([ new WelcomeCommand(), new TestsCommand(), new CodeStyleCommand(), - new FixturesCommand($entityManager), new DebugRouterCommand(), ]); +if (true === Environment::isLocal()) { + $application->addCommands([new FixturesCommand($entityManager)]); +} + $application->run(); diff --git a/api/mapas/app/config/di.php b/api/mapas/app/config/di.php new file mode 100644 index 0000000000..cd8dfcedcb --- /dev/null +++ b/api/mapas/app/config/di.php @@ -0,0 +1,70 @@ + fn () => new Serializer([new ObjectNormalizer()]), + ValidatorInterface::class => fn () => Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(), + ...repositories(), + ...services(), +]; + +function repositories(): array +{ + return [ + AgentRepositoryInterface::class => fn () => new AgentRepository(), + EventRepositoryInterface::class => fn () => new EventRepository(), + OpportunityRepositoryInterface::class => fn () => new OpportunityRepository(), + ProjectRepositoryInterface::class => fn () => new ProjectRepository(), + SealRepositoryInterface::class => fn () => new SealRepository(), + SpaceRepositoryInterface::class => fn () => new SpaceRepository(), + TermRepositoryInterface::class => fn () => new TermRepository(), + ]; +} + +function services(): array +{ + return [ + AgentServiceInterface::class => fn () => new AgentService(new AgentRepository(), new Serializer([new ObjectNormalizer()])), + EventServiceInterface::class => fn () => new EventService(new EventRepository(), new Serializer([new ObjectNormalizer()])), + OpportunityServiceInterface::class => fn () => new OpportunityService(new OpportunityRepository(), new Serializer([new ObjectNormalizer()])), + ProjectServiceInterface::class => fn () => new ProjectService(new ProjectRepository(), new Serializer([new ObjectNormalizer()])), + SealServiceInterface::class => fn () => new SealService(new AgentRepository(), new SealRepository(), new Serializer([new ObjectNormalizer()])), + SpaceServiceInterface::class => fn () => new SpaceService(new Serializer([new ObjectNormalizer()]), new SpaceRepository()), + TermServiceInterface::class => fn () => new TermService(new Serializer([new ObjectNormalizer()]), new TermRepository()), + ]; +} diff --git a/api/mapas/app/routes/api/agent.php b/api/mapas/app/routes/api/agent.php index 746f8414a5..02833c2c23 100644 --- a/api/mapas/app/routes/api/agent.php +++ b/api/mapas/app/routes/api/agent.php @@ -17,7 +17,7 @@ '/api/v2/agents/{id}' => [ Request::METHOD_GET => [AgentApiController::class, 'getOne'], Request::METHOD_PATCH => [AgentApiController::class, 'patch'], - Request::METHOD_DELETE => [AgentApiController::class, 'delete'], + Request::METHOD_DELETE => [AgentApiController::class, 'remove'], ], '/api/v2/agents/{id}/opportunities' => [ Request::METHOD_GET => [OpportunityApiController::class, 'getOpportunitiesByAgent'], diff --git a/api/mapas/app/routes/api/event.php b/api/mapas/app/routes/api/event.php index b898c66ebb..b5c526e545 100644 --- a/api/mapas/app/routes/api/event.php +++ b/api/mapas/app/routes/api/event.php @@ -16,6 +16,6 @@ '/api/v2/events/{id}' => [ Request::METHOD_GET => [EventApiController::class, 'getOne'], Request::METHOD_PATCH => [EventApiController::class, 'patch'], - Request::METHOD_DELETE => [EventApiController::class, 'delete'], + Request::METHOD_DELETE => [EventApiController::class, 'remove'], ], ]; diff --git a/api/mapas/app/routes/api/opportunity.php b/api/mapas/app/routes/api/opportunity.php index 792474eddd..02c77273ca 100644 --- a/api/mapas/app/routes/api/opportunity.php +++ b/api/mapas/app/routes/api/opportunity.php @@ -13,6 +13,6 @@ '/api/v2/opportunities/{id}' => [ Request::METHOD_GET => [OpportunityApiController::class, 'getOne'], Request::METHOD_PATCH => [OpportunityApiController::class, 'patch'], - Request::METHOD_DELETE => [OpportunityApiController::class, 'delete'], + Request::METHOD_DELETE => [OpportunityApiController::class, 'remove'], ], ]; diff --git a/api/mapas/app/routes/api/project.php b/api/mapas/app/routes/api/project.php index 4b6a364e22..cbe34db1b9 100644 --- a/api/mapas/app/routes/api/project.php +++ b/api/mapas/app/routes/api/project.php @@ -13,6 +13,6 @@ '/api/v2/projects/{id}' => [ Request::METHOD_GET => [ProjectApiController::class, 'getOne'], Request::METHOD_PATCH => [ProjectApiController::class, 'patch'], - Request::METHOD_DELETE => [ProjectApiController::class, 'delete'], + Request::METHOD_DELETE => [ProjectApiController::class, 'remove'], ], ]; diff --git a/api/mapas/app/routes/api/seal.php b/api/mapas/app/routes/api/seal.php index e26ea1230e..f287452b53 100644 --- a/api/mapas/app/routes/api/seal.php +++ b/api/mapas/app/routes/api/seal.php @@ -13,6 +13,6 @@ '/api/v2/seals/{id}' => [ Request::METHOD_GET => [SealApiController::class, 'getOne'], Request::METHOD_PATCH => [SealApiController::class, 'patch'], - Request::METHOD_DELETE => [SealApiController::class, 'delete'], + Request::METHOD_DELETE => [SealApiController::class, 'remove'], ], ]; diff --git a/api/mapas/app/routes/api/space.php b/api/mapas/app/routes/api/space.php index 1da6f78424..6bb18dc768 100644 --- a/api/mapas/app/routes/api/space.php +++ b/api/mapas/app/routes/api/space.php @@ -13,7 +13,7 @@ ], '/api/v2/spaces/{id}' => [ Request::METHOD_GET => [SpaceApiController::class, 'getOne'], - Request::METHOD_DELETE => [SpaceApiController::class, 'delete'], + Request::METHOD_DELETE => [SpaceApiController::class, 'remove'], Request::METHOD_PATCH => [SpaceApiController::class, 'patch'], ], '/api/v2/spaces/{id}/events' => [ diff --git a/api/mapas/app/routes/api/term.php b/api/mapas/app/routes/api/term.php index eaff38fd31..31e83fae96 100644 --- a/api/mapas/app/routes/api/term.php +++ b/api/mapas/app/routes/api/term.php @@ -8,8 +8,11 @@ return [ '/api/v2/terms' => [ Request::METHOD_GET => [TermApiController::class, 'getList'], + Request::METHOD_POST => [TermApiController::class, 'post'], ], '/api/v2/terms/{id}' => [ Request::METHOD_GET => [TermApiController::class, 'getOne'], + Request::METHOD_PATCH => [TermApiController::class, 'patch'], + Request::METHOD_DELETE => [TermApiController::class, 'remove'], ], ]; diff --git a/api/mapas/app/src/Application/Environment.php b/api/mapas/app/src/Application/Environment.php new file mode 100644 index 0000000000..cca4d19ee3 --- /dev/null +++ b/api/mapas/app/src/Application/Environment.php @@ -0,0 +1,36 @@ +load(self::getEnvData()); + + return self::LOCAL === self::getEnvinronment(); + } +} diff --git a/api/mapas/app/src/Command/FixturesCommand.php b/api/mapas/app/src/Command/FixturesCommand.php index 1b2a4ca3d6..f6b0ec6b66 100644 --- a/api/mapas/app/src/Command/FixturesCommand.php +++ b/api/mapas/app/src/Command/FixturesCommand.php @@ -25,10 +25,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeLn([ PHP_EOL, - '==============================', - '== RUN DATA FIXTURES ==', - '==============================', - PHP_EOL, + '=================================', + '=== RUNNING DATA FIXTURES ', ]); $loader = new Loader(); @@ -37,6 +35,12 @@ protected function execute(InputInterface $input, OutputInterface $output): int $executor = new ORMExecutor($this->entityManager, new ORMPurger()); $executor->execute($loader->getFixtures(), true); + $output->writeln([ + '=== '.count($loader->getFixtures()).' fixtures executed', + '=================================', + PHP_EOL, + ]); + return Command::SUCCESS; } diff --git a/api/mapas/app/src/Command/TestsCommand.php b/api/mapas/app/src/Command/TestsCommand.php index 6f81d48d2b..c8438c8afa 100644 --- a/api/mapas/app/src/Command/TestsCommand.php +++ b/api/mapas/app/src/Command/TestsCommand.php @@ -16,7 +16,8 @@ class TestsCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $output->writeln('-------------------------------------------------------'); - passthru("php vendor/bin/phpunit {$input->getArgument('path')}"); + passthru('php app/bin/console app:fixtures'); + passthru("php vendor/bin/phpunit {$input->getArgument('path')} --testdox --colors=always"); $output->writeln('-------------------------------------------------------'); return Command::SUCCESS; diff --git a/api/mapas/app/src/Controller/Api/AbstractApiController.php b/api/mapas/app/src/Controller/Api/AbstractApiController.php new file mode 100644 index 0000000000..12aaf0f171 --- /dev/null +++ b/api/mapas/app/src/Controller/Api/AbstractApiController.php @@ -0,0 +1,19 @@ +repository = new AgentRepository(); - - $this->agentService = new AgentService(); - $this->agentRequest = new AgentRequest(); + public function __construct( + private readonly AgentRepositoryInterface $repository, + private readonly AgentServiceInterface $agentService, + private readonly AgentRequest $agentRequest + ) { } public function getList(): JsonResponse @@ -36,63 +29,55 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $agent = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); - return new JsonResponse($agent); + return new JsonResponse( + $this->repository->find($id) + ); } public function getTypes(): JsonResponse { - $types = $this->agentService->getTypes(); - - return new JsonResponse($types); + return new JsonResponse( + $this->agentService->getTypes() + ); } public function post(): JsonResponse { - try { - $agentData = $this->agentRequest->validatePost(); - - $agent = $this->agentService->create((object) $agentData); - - $responseData = [ - 'id' => $agent->getId(), - 'name' => $agent->getName(), - 'shortDescription' => $agent->getShortDescription(), - 'terms' => $agent->getTerms(), - 'type' => $agent->getType(), - ]; - - return new JsonResponse($responseData, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + $agentData = $this->agentRequest->validatePost(); + + if (true === empty($agentData['name'])) { + throw new FieldRequiredException('name'); } + + $agent = $this->agentService->create((object) $agentData); + + $responseData = [ + 'id' => $agent->getId(), + 'name' => $agent->getName(), + 'shortDescription' => $agent->getShortDescription(), + 'terms' => $agent->getTerms(), + 'type' => $agent->getType(), + ]; + + return new JsonResponse($responseData, Response::HTTP_CREATED); } public function patch(array $params): JsonResponse { - try { - $agentData = $this->agentRequest->validateUpdate(); - $agent = $this->agentService->update((int) $params['id'], (object) $agentData); - - return new JsonResponse($agent, Response::HTTP_CREATED); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $id = $this->extractIdParam($params); + $agentData = $this->agentRequest->validateUpdate(); + $agent = $this->agentService->update($id, (object) $agentData); + + return new JsonResponse($agent, Response::HTTP_CREATED); } - public function delete(array $params): JsonResponse + public function remove(array $params): JsonResponse { - try { - $this->agentService->discard((int) $params['id']); - - return new JsonResponse(status: Response::HTTP_NO_CONTENT); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $id = $this->extractIdParam($params); + $this->agentService->removeById($id); + + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/EventApiController.php b/api/mapas/app/src/Controller/Api/EventApiController.php index da193e9403..457f2deea0 100644 --- a/api/mapas/app/src/Controller/Api/EventApiController.php +++ b/api/mapas/app/src/Controller/Api/EventApiController.php @@ -4,26 +4,20 @@ namespace App\Controller\Api; -use App\Repository\EventRepository; +use App\Exception\FieldRequiredException; +use App\Repository\Interface\EventRepositoryInterface; use App\Request\EventRequest; -use App\Service\EventService; -use Exception; +use App\Service\Interface\EventServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -class EventApiController +class EventApiController extends AbstractApiController { - private EventService $eventService; - private EventRepository $repository; - private EventRequest $eventRequest; - - public function __construct() - { - $this->eventService = new EventService(); - - $this->repository = new EventRepository(); - - $this->eventRequest = new EventRequest(); + public function __construct( + private readonly EventRepositoryInterface $repository, + private readonly EventServiceInterface $eventService, + private readonly EventRequest $eventRequest + ) { } public function getList(): JsonResponse @@ -35,7 +29,8 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $event = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $event = $this->repository->find($id); return new JsonResponse($event); } @@ -49,53 +44,52 @@ public function getTypes(): JsonResponse public function getEventsBySpace(array $params): JsonResponse { - $events = $this->repository->findEventsBySpaceId((int) $params['id']); + $id = $this->extractIdParam($params); + $events = $this->repository->findEventsBySpaceId($id); return new JsonResponse($events); } public function post(): JsonResponse { - try { - $eventData = $this->eventRequest->validatePost(); - - $event = $this->eventService->create((object) $eventData); - - $responseData = [ - 'id' => $event->getId(), - 'name' => $event->getName(), - 'shortDescription' => $event->getShortDescription(), - 'classificacaoEtaria' => $event->getMetadata('classificacaoEtaria'), - 'terms' => $event->getTerms(), - ]; - - return new JsonResponse($responseData, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + $eventData = $this->eventRequest->validatePost(); + + if (true === empty($eventData)) { + throw new FieldRequiredException('Event data is required.'); } + + $event = $this->eventService->create((object) $eventData); + + $responseData = [ + 'id' => $event->getId(), + 'name' => $event->getName(), + 'shortDescription' => $event->getShortDescription(), + 'classificacaoEtaria' => $event->getMetadata('classificacaoEtaria'), + 'terms' => $event->getTerms(), + ]; + + return new JsonResponse($responseData, Response::HTTP_CREATED); } public function patch(array $params): JsonResponse { - try { - $eventData = $this->eventRequest->validateUpdate(); - $event = $this->eventService->update((int) $params['id'], (object) $eventData); + $id = $this->extractIdParam($params); + $eventData = $this->eventRequest->validateUpdate(); - return new JsonResponse($event, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + if (true === empty($eventData)) { + throw new FieldRequiredException('Event data is required.'); } + + $event = $this->eventService->update($id, (object) $eventData); + + return new JsonResponse($event, Response::HTTP_CREATED); } - public function delete($params): JsonResponse + public function remove(array $params): JsonResponse { - try { - $event = $this->eventRequest->validateEventExistent($params); - $this->repository->softDelete($event); + $id = $this->extractIdParam($params); + $this->eventService->removeById($id); - return new JsonResponse([], Response::HTTP_OK); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/OpportunityApiController.php b/api/mapas/app/src/Controller/Api/OpportunityApiController.php index e7c82399df..95905cb3aa 100644 --- a/api/mapas/app/src/Controller/Api/OpportunityApiController.php +++ b/api/mapas/app/src/Controller/Api/OpportunityApiController.php @@ -4,24 +4,19 @@ namespace App\Controller\Api; -use App\Repository\OpportunityRepository; +use App\Repository\Interface\OpportunityRepositoryInterface; use App\Request\OpportunityRequest; -use App\Service\OpportunityService; -use Exception; +use App\Service\Interface\OpportunityServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -class OpportunityApiController +class OpportunityApiController extends AbstractApiController { - private OpportunityRequest $opportunityRequest; - private OpportunityService $opportunityService; - private OpportunityRepository $repository; - - public function __construct() - { - $this->repository = new OpportunityRepository(); - $this->opportunityRequest = new OpportunityRequest(); - $this->opportunityService = new OpportunityService(); + public function __construct( + private readonly OpportunityRepositoryInterface $repository, + private readonly OpportunityServiceInterface $opportunityService, + private readonly OpportunityRequest $opportunityRequest + ) { } public function getList(): JsonResponse @@ -33,33 +28,30 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $opportunity = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $opportunity = $this->repository->find($id); return new JsonResponse($opportunity); } public function post(): JsonResponse { - try { - $opportunityData = $this->opportunityRequest->validatePost(); - $opportunity = $this->opportunityService->create((object) $opportunityData); - - $responseData = [ - 'id' => $opportunity->getId(), - 'name' => $opportunity->getName(), - 'terms' => $opportunity->getTerms(), - '_type' => $opportunity->getType(), - ]; - - return new JsonResponse($responseData, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $opportunityData = $this->opportunityRequest->validatePost(); + $opportunity = $this->opportunityService->create((object) $opportunityData); + + $responseData = [ + 'id' => $opportunity->getId(), + 'name' => $opportunity->getName(), + 'terms' => $opportunity->getTerms(), + '_type' => $opportunity->getType(), + ]; + + return new JsonResponse($responseData, Response::HTTP_CREATED); } public function getOpportunitiesByAgent(array $params): JsonResponse { - $agentId = (int) $params['id']; + $agentId = $this->extractIdParam($params); $opportunities = $this->repository->findOpportunitiesByAgentId($agentId); return new JsonResponse($opportunities); @@ -67,25 +59,18 @@ public function getOpportunitiesByAgent(array $params): JsonResponse public function patch(array $params): JsonResponse { - try { - $opportunityData = $this->opportunityRequest->validateUpdate(); - $opportunity = $this->opportunityService->update((int) $params['id'], (object) $opportunityData); - - return new JsonResponse($opportunity, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $id = $this->extractIdParam($params); + $opportunityData = $this->opportunityRequest->validateUpdate(); + $opportunity = $this->opportunityService->update($id, (object) $opportunityData); + + return new JsonResponse($opportunity, Response::HTTP_CREATED); } - public function delete(array $params): JsonResponse + public function remove(array $params): JsonResponse { - try { - $opportunity = $this->opportunityRequest->validateDelete($params); - $this->repository->softDelete($opportunity); - - return new JsonResponse([], Response::HTTP_NO_CONTENT); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $id = $this->extractIdParam($params); + $this->opportunityService->removeById($id); + + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/ProjectApiController.php b/api/mapas/app/src/Controller/Api/ProjectApiController.php index b77f9b7cc2..c3f4b7cdae 100644 --- a/api/mapas/app/src/Controller/Api/ProjectApiController.php +++ b/api/mapas/app/src/Controller/Api/ProjectApiController.php @@ -4,27 +4,19 @@ namespace App\Controller\Api; -use App\Exception\ResourceNotFoundException; -use App\Repository\ProjectRepository; +use App\Repository\Interface\ProjectRepositoryInterface; use App\Request\ProjectRequest; -use App\Service\ProjectService; -use Exception; +use App\Service\Interface\ProjectServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -class ProjectApiController +class ProjectApiController extends AbstractApiController { - public ProjectService $projectService; - private ProjectRequest $projectRequest; - - private ProjectRepository $repository; - - public function __construct() - { - $this->repository = new ProjectRepository(); - - $this->projectService = new ProjectService(); - $this->projectRequest = new ProjectRequest(); + public function __construct( + private readonly ProjectRepositoryInterface $repository, + private readonly ProjectServiceInterface $projectService, + private readonly ProjectRequest $projectRequest + ) { } public function getList(): JsonResponse @@ -36,54 +28,42 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $project = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $project = $this->repository->find($id); return new JsonResponse($project); } public function post(): JsonResponse { - try { - $projectData = $this->projectRequest->validatePost(); - - $project = $this->projectService->create((object) $projectData); - - $responseData = [ - 'id' => $project->getId(), - 'name' => $project->getName(), - 'shortDescription' => $project->getShortDescription(), - 'type' => $project->getType(), - ]; - - return new JsonResponse($responseData, status: Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], status: Response::HTTP_BAD_REQUEST); - } + $projectData = $this->projectRequest->validatePost(); + + $project = $this->projectService->create((object) $projectData); + + $responseData = [ + 'id' => $project->getId(), + 'name' => $project->getName(), + 'shortDescription' => $project->getShortDescription(), + 'type' => $project->getType(), + ]; + + return new JsonResponse($responseData, status: Response::HTTP_CREATED); } public function patch(array $params): JsonResponse { - try { - $projectData = $this->projectRequest->validateUpdate(); + $id = $this->extractIdParam($params); + $projectData = $this->projectRequest->validateUpdate(); + $project = $this->projectService->update($id, (object) $projectData); - $project = $this->projectService->update((int) $params['id'], (object) $projectData); - - return new JsonResponse($project, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + return new JsonResponse($project, Response::HTTP_CREATED); } - public function delete(array $params): JsonResponse + public function remove(array $params): JsonResponse { - try { - $this->projectService->discard((int) $params['id']); - - return new JsonResponse(status: Response::HTTP_NO_CONTENT); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + $id = $this->extractIdParam($params); + $this->projectService->removeById($id); + + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/SealApiController.php b/api/mapas/app/src/Controller/Api/SealApiController.php index 7cf6adbee4..5bc44bc7c8 100644 --- a/api/mapas/app/src/Controller/Api/SealApiController.php +++ b/api/mapas/app/src/Controller/Api/SealApiController.php @@ -4,27 +4,19 @@ namespace App\Controller\Api; -use App\Exception\ResourceNotFoundException; -use App\Exception\ValidatorException; -use App\Repository\SealRepository; +use App\Repository\Interface\SealRepositoryInterface; use App\Request\SealRequest; -use App\Service\SealService; -use Exception; -use InvalidArgumentException; +use App\Service\Interface\SealServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -class SealApiController +class SealApiController extends AbstractApiController { - private SealRepository $repository; - private SealService $sealService; - private SealRequest $sealRequest; - - public function __construct() - { - $this->repository = new SealRepository(); - $this->sealService = new SealService(); - $this->sealRequest = new SealRequest(); + public function __construct( + private readonly SealRepositoryInterface $repository, + private readonly SealServiceInterface $sealService, + private readonly SealRequest $sealRequest + ) { } public function getList(): JsonResponse @@ -36,55 +28,34 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $seal = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $seal = $this->repository->find($id); return new JsonResponse($seal); } public function post(): JsonResponse { - try { - $sealData = $this->sealRequest->validatePost(); - $seal = $this->sealService->create($sealData); + $sealData = $this->sealRequest->validatePost(); + $seal = $this->sealService->create($sealData); - return new JsonResponse($seal, Response::HTTP_CREATED); - } catch (ValidatorException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - 'fields' => $exception->getFields(), - ], Response::HTTP_BAD_REQUEST); - } catch (InvalidArgumentException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + return new JsonResponse($seal, Response::HTTP_CREATED); } public function patch(array $params): JsonResponse { - try { - $sealData = $this->sealRequest->validatePatch(); - $seal = $this->sealService->update((int) $params['id'], (object) $sealData); + $id = $this->extractIdParam($params); + $sealData = $this->sealRequest->validatePatch(); + $seal = $this->sealService->update($id, (object) $sealData); - return new JsonResponse($seal, Response::HTTP_CREATED); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } catch (ValidatorException $exception) { - return new JsonResponse([ - 'error' => $exception->getMessage(), - 'fields' => $exception->getFields(), - ], Response::HTTP_BAD_REQUEST); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); - } + return new JsonResponse($seal, Response::HTTP_CREATED); } - public function delete(array $params): JsonResponse + public function remove(array $params): JsonResponse { - try { - $this->sealService->delete((int) $params['id']); + $id = $this->extractIdParam($params); + $this->sealService->removeById($id); - return new JsonResponse(null, Response::HTTP_NO_CONTENT); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/SpaceApiController.php b/api/mapas/app/src/Controller/Api/SpaceApiController.php index fb4a6b6f4b..2101154ff4 100644 --- a/api/mapas/app/src/Controller/Api/SpaceApiController.php +++ b/api/mapas/app/src/Controller/Api/SpaceApiController.php @@ -4,26 +4,21 @@ namespace App\Controller\Api; -use App\Enum\EntityStatusEnum; -use App\Exception\ResourceNotFoundException; -use App\Repository\SpaceRepository; +use App\Exception\FieldInvalidException; +use App\Exception\FieldRequiredException; +use App\Repository\Interface\SpaceRepositoryInterface; use App\Request\SpaceRequest; -use App\Service\SpaceService; -use Exception; +use App\Service\Interface\SpaceServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; -class SpaceApiController +class SpaceApiController extends AbstractApiController { - private SpaceRepository $repository; - private SpaceRequest $spaceRequest; - private SpaceService $spaceService; - - public function __construct() - { - $this->repository = new SpaceRepository(); - $this->spaceRequest = new SpaceRequest(); - $this->spaceService = new SpaceService(); + public function __construct( + private readonly SpaceRepositoryInterface $repository, + private readonly SpaceServiceInterface $spaceService, + private readonly SpaceRequest $spaceRequest + ) { } public function getList(): JsonResponse @@ -35,55 +30,60 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $space = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $space = $this->repository->find($id); return new JsonResponse($space); } public function post(): JsonResponse { - try { - $spaceData = $this->spaceRequest->validatePost(); - $space = $this->spaceService->create((object) $spaceData); - - $responseData = [ - 'id' => $space->getId(), - 'name' => $space->getName(), - 'shortDescription' => $space->getShortDescription(), - 'terms' => $space->getTerms(), - 'type' => $space->getType(), - ]; - - return new JsonResponse($responseData, Response::HTTP_CREATED); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + $spaceData = $this->spaceRequest->validatePost(); + + if (true === empty($spaceData['name'])) { + throw new FieldRequiredException('name'); } - } - public function patch(array $params): JsonResponse - { - try { - $spaceData = $this->spaceRequest->validateUpdate(); - $space = $this->spaceService->update((int) $params['id'], (object) $spaceData); - - return new JsonResponse($space, Response::HTTP_CREATED); - } catch (ResourceNotFoundException $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); - } catch (Exception $exception) { - return new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + if (false === is_string($spaceData['name'])) { + throw new FieldInvalidException('name'); } + + $space = $this->spaceService->create((object) $spaceData); + + $responseData = [ + 'id' => $space->getId(), + 'name' => $space->getName(), + 'shortDescription' => $space->getShortDescription(), + 'terms' => $space->getTerms(), + 'type' => $space->getType(), + ]; + + return new JsonResponse($responseData, Response::HTTP_CREATED); } - public function delete(array $params): JsonResponse + public function patch(array $params): JsonResponse { - $space = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $spaceData = $this->spaceRequest->validateUpdate(); + + if (true === empty($spaceData['name'])) { + throw new FieldRequiredException('name'); + } - if (EntityStatusEnum::TRASH->getValue() === $space->status) { - return new JsonResponse(['error' => 'Espaço não encontrado'], Response::HTTP_NOT_FOUND); + if (false === is_string($spaceData['name'])) { + throw new FieldInvalidException('name'); } - $this->repository->softDelete($space); + $space = $this->spaceService->update($id, (object) $spaceData); + + return new JsonResponse($space, Response::HTTP_CREATED); + } + + public function remove(array $params): JsonResponse + { + $id = $this->extractIdParam($params); + $this->spaceService->removeById($id); - return new JsonResponse(null, Response::HTTP_NO_CONTENT); + return new JsonResponse(status: Response::HTTP_NO_CONTENT); } } diff --git a/api/mapas/app/src/Controller/Api/TermApiController.php b/api/mapas/app/src/Controller/Api/TermApiController.php index 004ca40def..cfaced5081 100644 --- a/api/mapas/app/src/Controller/Api/TermApiController.php +++ b/api/mapas/app/src/Controller/Api/TermApiController.php @@ -4,16 +4,19 @@ namespace App\Controller\Api; -use App\Repository\TermRepository; +use App\Repository\Interface\TermRepositoryInterface; +use App\Request\TermRequest; +use App\Service\Interface\TermServiceInterface; use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\HttpFoundation\Response; -class TermApiController +class TermApiController extends AbstractApiController { - private TermRepository $repository; - - public function __construct() - { - $this->repository = new TermRepository(); + public function __construct( + private readonly TermRepositoryInterface $repository, + private readonly TermRequest $termRequest, + private readonly TermServiceInterface $service + ) { } public function getList(): JsonResponse @@ -25,8 +28,34 @@ public function getList(): JsonResponse public function getOne(array $params): JsonResponse { - $term = $this->repository->find((int) $params['id']); + $id = $this->extractIdParam($params); + $term = $this->repository->find($id); return new JsonResponse($term); } + + public function post(): JsonResponse + { + $termData = $this->termRequest->validatePost(); + $term = $this->service->create($termData); + + return new JsonResponse($term, Response::HTTP_CREATED); + } + + public function patch(array $params): JsonResponse + { + $id = $this->extractIdParam($params); + $termData = $this->termRequest->validatePatch(); + $term = $this->service->update($id, $termData); + + return new JsonResponse($term, Response::HTTP_OK); + } + + public function remove(array $params): JsonResponse + { + $id = $this->extractIdParam($params); + $this->service->removeById($id); + + return new JsonResponse(status: Response::HTTP_NO_CONTENT); + } } diff --git a/api/mapas/app/src/DTO/TermDto.php b/api/mapas/app/src/DTO/TermDto.php new file mode 100644 index 0000000000..91be9eda56 --- /dev/null +++ b/api/mapas/app/src/DTO/TermDto.php @@ -0,0 +1,27 @@ + 2])], groups: ['patch', 'post'])] + public mixed $taxonomy; + + #[Sequentially([new NotBlank()], groups: ['post'])] + #[Sequentially([new Type('string'), new Length(['min' => 2]), new UniqueInEntity(Term::class, 'term')], groups: ['patch', 'post'])] + public mixed $term; + + #[Sequentially([new NotBlank()], groups: ['post'])] + #[Sequentially([new Type('string'), new Length(['min' => 2])], groups: ['patch', 'post'])] + public mixed $description; +} diff --git a/api/mapas/app/src/Entity/Term.php b/api/mapas/app/src/Entity/Term.php new file mode 100644 index 0000000000..7efdd2fcec --- /dev/null +++ b/api/mapas/app/src/Entity/Term.php @@ -0,0 +1,65 @@ +id; + } + + public function getTaxonomy(): string + { + return $this->taxonomy; + } + + public function setTaxonomy(string $taxonomy): void + { + $this->taxonomy = $taxonomy; + } + + public function getTerm(): string + { + return $this->term; + } + + public function setTerm(string $term): void + { + $this->term = $term; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): void + { + $this->description = $description; + } +} diff --git a/api/mapas/app/src/Kernel.php b/api/mapas/app/src/Kernel.php index eac2115e02..bb13a3d176 100644 --- a/api/mapas/app/src/Kernel.php +++ b/api/mapas/app/src/Kernel.php @@ -4,6 +4,15 @@ namespace App; +use App\Exception\FieldInvalidException; +use App\Exception\FieldRequiredException; +use App\Exception\InvalidRequestException; +use App\Exception\RequiredIdParamException; +use App\Exception\ResourceNotFoundException as InternalResourceNotFoundException; +use App\Exception\ValidatorException; +use DI\ContainerBuilder; +use Error; +use Exception; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\MethodNotAllowedException; @@ -33,13 +42,13 @@ public function execute(): void $matcher = new UrlMatcher($this->routes, $context); $this->dispatchAction($matcher); - } catch (MethodNotAllowedException $exception) { + } catch (MethodNotAllowedException) { (new JsonResponse([ 'error' => 'Method not allowed: '.$_SERVER['REQUEST_METHOD'], ], status: Response::HTTP_METHOD_NOT_ALLOWED))->send(); exit; - } catch (ResourceNotFoundException $exception) { + } catch (ResourceNotFoundException) { return; } } @@ -58,7 +67,26 @@ private function dispatchAction(UrlMatcher $matcher): void unset($parameters['_route']); - $response = (new $controller())->$method($parameters); + $builder = new ContainerBuilder(); + $builder->addDefinitions(dirname(__DIR__).'/config/di.php'); + $container = $builder->build(); + + $controller = $container->get($controller); + + try { + $response = $controller->$method($parameters); + } catch (InternalResourceNotFoundException $exception) { + $response = new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_NOT_FOUND); + } catch (ValidatorException $exception) { + $response = new JsonResponse([ + 'error' => $exception->getMessage(), + 'fields' => $exception->getFields(), + ], Response::HTTP_BAD_REQUEST); + } catch (FieldInvalidException|FieldRequiredException|RequiredIdParamException|InvalidRequestException $exception) { + $response = new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + } catch (Exception|Error $exception) { + $response = new JsonResponse(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST); + } if ($response instanceof Response) { $response->send(); diff --git a/api/mapas/app/src/Repository/AgentRepository.php b/api/mapas/app/src/Repository/AgentRepository.php index ab53c0a1ea..c8328178bf 100644 --- a/api/mapas/app/src/Repository/AgentRepository.php +++ b/api/mapas/app/src/Repository/AgentRepository.php @@ -5,10 +5,12 @@ namespace App\Repository; use App\Enum\EntityStatusEnum; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\AgentRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Agent; -class AgentRepository extends AbstractRepository +class AgentRepository extends AbstractRepository implements AgentRepositoryInterface { private ObjectRepository $repository; @@ -22,13 +24,24 @@ public function findAll(): array { return $this->repository ->createQueryBuilder('agent') + ->where('agent.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Agent + public function find(int $id): Agent { - return $this->repository->find($id); + $agent = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED->getValue(), + ]); + + if (null === $agent) { + throw new ResourceNotFoundException(); + } + + return $agent; } public function save(Agent $agent): void @@ -37,7 +50,7 @@ public function save(Agent $agent): void $this->mapaCulturalEntityManager->flush(); } - public function softDelete(Agent $agent): void + public function remove(Agent $agent): void { $agent->setStatus(EntityStatusEnum::TRASH->getValue()); $this->mapaCulturalEntityManager->persist($agent); diff --git a/api/mapas/app/src/Repository/EventRepository.php b/api/mapas/app/src/Repository/EventRepository.php index 7da1da0838..e65f1149fb 100644 --- a/api/mapas/app/src/Repository/EventRepository.php +++ b/api/mapas/app/src/Repository/EventRepository.php @@ -5,11 +5,13 @@ namespace App\Repository; use App\Enum\EntityStatusEnum; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\EventRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Event; use MapasCulturais\Entities\EventOccurrence; -class EventRepository extends AbstractRepository +class EventRepository extends AbstractRepository implements EventRepositoryInterface { private ObjectRepository $repository; @@ -23,18 +25,29 @@ public function findAll(): array { return $this->repository ->createQueryBuilder('events') + ->where('events.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Event + public function find(int $id): Event { - return $this->repository->find($id); + $event = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED->getValue(), + ]); + + if (null === $event) { + throw new ResourceNotFoundException(); + } + + return $event; } public function findEventsBySpaceId(int $spaceId): array { - $queryBuilder = $this->getEntityManager() + $queryBuilder = $this->entityManager ->createQueryBuilder() ->select([ 'e.id', @@ -61,10 +74,11 @@ public function save($event): void $event->save(); } - public function softDelete(Event $event): void + public function remove(Event $event): void { $event->setStatus(EntityStatusEnum::TRASH->getValue()); - $this->save($event); + $this->mapaCulturalEntityManager->persist($event); + $this->mapaCulturalEntityManager->flush(); } public function update(Event $event): void diff --git a/api/mapas/app/src/Repository/Interface/AgentRepositoryInterface.php b/api/mapas/app/src/Repository/Interface/AgentRepositoryInterface.php new file mode 100644 index 0000000000..2ab51e6e1e --- /dev/null +++ b/api/mapas/app/src/Repository/Interface/AgentRepositoryInterface.php @@ -0,0 +1,18 @@ +repository ->createQueryBuilder('opportunity') + ->where('opportunity.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Opportunity + public function find(int $id): Opportunity { - return $this->repository->find($id); + $opportunity = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED, + ]); + + if (null === $opportunity) { + throw new ResourceNotFoundException(); + } + + return $opportunity; } public function save(Opportunity $opportunity): void @@ -40,7 +53,7 @@ public function save(Opportunity $opportunity): void public function findOpportunitiesByAgentId(int $agentId): array { - $queryBuilder = $this->getEntityManager() + $queryBuilder = $this->entityManager ->createQueryBuilder() ->select('ao') ->from(AgentOpportunity::class, 'ao') @@ -51,9 +64,10 @@ public function findOpportunitiesByAgentId(int $agentId): array return $queryBuilder->getQuery()->getArrayResult(); } - public function softDelete(Opportunity $opportunity): void + public function remove(Opportunity $opportunity): void { $opportunity->setStatus(EntityStatusEnum::TRASH->getValue()); - $this->save($opportunity); + $this->mapaCulturalEntityManager->persist($opportunity); + $this->mapaCulturalEntityManager->flush(); } } diff --git a/api/mapas/app/src/Repository/ProjectRepository.php b/api/mapas/app/src/Repository/ProjectRepository.php index 5564604477..f6cde719a1 100644 --- a/api/mapas/app/src/Repository/ProjectRepository.php +++ b/api/mapas/app/src/Repository/ProjectRepository.php @@ -5,10 +5,12 @@ namespace App\Repository; use App\Enum\EntityStatusEnum; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\ProjectRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Project; -class ProjectRepository extends AbstractRepository +class ProjectRepository extends AbstractRepository implements ProjectRepositoryInterface { private ObjectRepository $repository; @@ -22,13 +24,24 @@ public function findAll(): array { return $this->repository ->createQueryBuilder('project') + ->where('project.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Project + public function find(int $id): Project { - return $this->repository->find($id); + $project = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED->getValue(), + ]); + + if (null === $project) { + throw new ResourceNotFoundException(); + } + + return $project; } public function save(Project $project): void @@ -37,7 +50,7 @@ public function save(Project $project): void $this->mapaCulturalEntityManager->flush(); } - public function softDelete(Project $project): void + public function remove(Project $project): void { $project->setStatus(EntityStatusEnum::TRASH->getValue()); $this->mapaCulturalEntityManager->persist($project); diff --git a/api/mapas/app/src/Repository/SealRepository.php b/api/mapas/app/src/Repository/SealRepository.php index 6c45495905..bd46758d4b 100644 --- a/api/mapas/app/src/Repository/SealRepository.php +++ b/api/mapas/app/src/Repository/SealRepository.php @@ -5,10 +5,12 @@ namespace App\Repository; use App\Enum\EntityStatusEnum; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\SealRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Seal; -class SealRepository extends AbstractRepository +class SealRepository extends AbstractRepository implements SealRepositoryInterface { private ObjectRepository $repository; @@ -23,13 +25,24 @@ public function findAll(): array { return $this->repository ->createQueryBuilder('seal') + ->where('seal.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Seal + public function find(int $id): Seal { - return $this->repository->find($id); + $seal = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED->getValue(), + ]); + + if (null === $seal) { + throw new ResourceNotFoundException(); + } + + return $seal; } public function save(Seal $seal): void @@ -38,10 +51,10 @@ public function save(Seal $seal): void $this->mapaCulturalEntityManager->flush(); } - public function softDelete(Seal $space): void + public function remove(Seal $seal): void { - $space->setStatus(EntityStatusEnum::TRASH->getValue()); - $this->mapaCulturalEntityManager->persist($space); + $seal->setStatus(EntityStatusEnum::TRASH->getValue()); + $this->mapaCulturalEntityManager->persist($seal); $this->mapaCulturalEntityManager->flush(); } } diff --git a/api/mapas/app/src/Repository/SpaceRepository.php b/api/mapas/app/src/Repository/SpaceRepository.php index 618682db4a..b78e83a107 100644 --- a/api/mapas/app/src/Repository/SpaceRepository.php +++ b/api/mapas/app/src/Repository/SpaceRepository.php @@ -5,10 +5,12 @@ namespace App\Repository; use App\Enum\EntityStatusEnum; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\SpaceRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Space; -class SpaceRepository extends AbstractRepository +class SpaceRepository extends AbstractRepository implements SpaceRepositoryInterface { private ObjectRepository $repository; @@ -30,16 +32,27 @@ public function findAll(): array { return $this->repository ->createQueryBuilder('space') + ->where('space.status = :status') + ->setParameter('status', EntityStatusEnum::ENABLED->getValue()) ->getQuery() ->getArrayResult(); } - public function find(int $id): ?Space + public function find(int $id): Space { - return $this->repository->find($id); + $space = $this->repository->findOneBy([ + 'id' => $id, + 'status' => EntityStatusEnum::ENABLED->getValue(), + ]); + + if (null === $space) { + throw new ResourceNotFoundException(); + } + + return $space; } - public function softDelete(Space $space): void + public function remove(Space $space): void { $space->setStatus(EntityStatusEnum::TRASH->getValue()); $this->mapaCulturalEntityManager->persist($space); diff --git a/api/mapas/app/src/Repository/TermRepository.php b/api/mapas/app/src/Repository/TermRepository.php index de211d6c63..7bc8b95931 100644 --- a/api/mapas/app/src/Repository/TermRepository.php +++ b/api/mapas/app/src/Repository/TermRepository.php @@ -4,10 +4,12 @@ namespace App\Repository; +use App\Exception\ResourceNotFoundException; +use App\Repository\Interface\TermRepositoryInterface; use Doctrine\Persistence\ObjectRepository; use MapasCulturais\Entities\Term; -class TermRepository extends AbstractRepository +class TermRepository extends AbstractRepository implements TermRepositoryInterface { private ObjectRepository $repository; @@ -26,9 +28,17 @@ public function findAll(): array ->getArrayResult(); } - public function find(int $id): ?Term + public function find(int $id): Term { - return $this->repository->find($id); + $term = $this->repository->findOneBy([ + 'id' => $id, + ]); + + if (null === $term) { + throw new ResourceNotFoundException(); + } + + return $term; } public function save(Term $term): void @@ -36,4 +46,10 @@ public function save(Term $term): void $this->mapaCulturalEntityManager->persist($term); $this->mapaCulturalEntityManager->flush(); } + + public function remove(Term $term): void + { + $this->mapaCulturalEntityManager->remove($term); + $this->mapaCulturalEntityManager->flush(); + } } diff --git a/api/mapas/app/src/Request/AgentRequest.php b/api/mapas/app/src/Request/AgentRequest.php index e0ec1ae7ed..b77f764023 100644 --- a/api/mapas/app/src/Request/AgentRequest.php +++ b/api/mapas/app/src/Request/AgentRequest.php @@ -4,33 +4,36 @@ namespace App\Request; -use Exception; +use App\Exception\FieldRequiredException; +use App\Exception\InvalidRequestException; use Symfony\Component\HttpFoundation\Request; class AgentRequest { - protected Request $request; - - public function __construct() - { - $this->request = new Request(); + public function __construct( + private readonly Request $request + ) { } public function validatePost(): array { $jsonData = $this->request->getContent(); - $data = json_decode($jsonData, true); + $data = json_decode($jsonData, associative: true); $requiredFields = ['type', 'name', 'shortDescription', 'terms']; foreach ($requiredFields as $field) { - if (!isset($data[$field])) { - throw new Exception(ucfirst($field).' is required'); + if (false === isset($data[$field])) { + throw new FieldRequiredException(ucfirst($field)); } } - if (!isset($data['type']) || !is_array($data['terms']) || !is_array($data['terms']['area'])) { - throw new Exception('The "terms" field must be an object with a property "area" which is an array.'); + if ( + false === isset($data['type']) + || false === is_array($data['terms']) + || false === is_array($data['terms']['area']) + ) { + throw new InvalidRequestException('The "terms" field must be an object with a property "area" which is an array.'); } return $data; diff --git a/api/mapas/app/src/Request/EventRequest.php b/api/mapas/app/src/Request/EventRequest.php index 820e0d7039..ae2418f46a 100644 --- a/api/mapas/app/src/Request/EventRequest.php +++ b/api/mapas/app/src/Request/EventRequest.php @@ -4,21 +4,15 @@ namespace App\Request; -use App\Enum\EntityStatusEnum; -use App\Repository\EventRepository; -use Exception; -use MapasCulturais\Entities\Event; +use App\Exception\FieldRequiredException; +use App\Exception\InvalidRequestException; use Symfony\Component\HttpFoundation\Request; class EventRequest { - protected Request $request; - private $repository; - - public function __construct() - { - $this->request = new Request(); - $this->repository = new EventRepository(); + public function __construct( + private readonly Request $request + ) { } public function validatePost(): array @@ -28,33 +22,26 @@ public function validatePost(): array $requiredFields = ['name', 'shortDescription', 'classificacaoEtaria', 'terms']; foreach ($requiredFields as $field) { - if (!isset($data[$field])) { - throw new Exception(ucfirst($field).' is required.'); + if (false === isset($data[$field])) { + throw new FieldRequiredException(ucfirst($field)); } } - if (!isset($data['terms']['linguagem']) || !is_array($data['terms']) || !$data['terms']['linguagem']) { - throw new Exception('The "terms" field must be an object with a property "linguagem" which is an array.'); + if ( + false === isset($data['terms']['linguagem']) + || false === is_array($data['terms']) + || false === is_array($data['terms']['linguagem']) + ) { + throw new InvalidRequestException('The "terms" field must be an object with a property "linguagem" which is an array.'); } return $data; } - public function validateEventExistent(array $params): Event - { - $event = $this->repository->find((int) $params['id']); - - if (!$event || EntityStatusEnum::TRASH->getValue() === $event->status) { - throw new Exception('Event not found or already deleted.'); - } - - return $event; - } - public function validateUpdate(): array { $jsonData = $this->request->getContent(); - $data = json_decode($jsonData, true); + $data = json_decode($jsonData, associative: true); return $data; } diff --git a/api/mapas/app/src/Request/OpportunityRequest.php b/api/mapas/app/src/Request/OpportunityRequest.php index 193c841dad..532cd07687 100644 --- a/api/mapas/app/src/Request/OpportunityRequest.php +++ b/api/mapas/app/src/Request/OpportunityRequest.php @@ -4,17 +4,15 @@ namespace App\Request; -use App\Enum\EntityStatusEnum; -use Exception; +use App\Exception\FieldRequiredException; +use App\Exception\InvalidRequestException; use Symfony\Component\HttpFoundation\Request; class OpportunityRequest { - protected Request $request; - - public function __construct() - { - $this->request = new Request(); + public function __construct( + private readonly Request $request + ) { } public function validatePost(): array @@ -25,15 +23,15 @@ public function validatePost(): array $requiredFields = ['objectType', 'name', 'terms', 'type']; foreach ($requiredFields as $field) { - if (!isset($data[$field]) || empty($data[$field])) { - throw new Exception(ucfirst($field).' is required.'); + if (false === isset($data[$field]) || true === empty($data[$field])) { + throw new FieldRequiredException(ucfirst($field)); } } $linkFields = ['project', 'event', 'space', 'agent']; foreach ($linkFields as $field) { - if (isset($data[$field]) && !is_array($data[$field])) { - throw new Exception(ucfirst($field).' must be an array if provided.'); + if (true === isset($data[$field]) && false === is_array($data[$field])) { + throw new InvalidRequestException(ucfirst($field).' must be an array if provided.'); } } @@ -47,19 +45,4 @@ public function validateUpdate(): array associative: true ); } - - public function validateDelete(array $params): Opportunity - { - if (!isset($params['id'])) { - throw new Exception('ID is required.'); - } - - $opportunity = $this->repository->find((int) $params['id']); - - if (!$opportunity || EntityStatusEnum::TRASH->getValue() === $opportunity->status) { - throw new Exception('Opportunity not found or already deleted.'); - } - - return $opportunity; - } } diff --git a/api/mapas/app/src/Request/ProjectRequest.php b/api/mapas/app/src/Request/ProjectRequest.php index ae9b7d8ba1..2476917f72 100644 --- a/api/mapas/app/src/Request/ProjectRequest.php +++ b/api/mapas/app/src/Request/ProjectRequest.php @@ -4,16 +4,14 @@ namespace App\Request; -use Exception; +use App\Exception\FieldRequiredException; use Symfony\Component\HttpFoundation\Request; class ProjectRequest { - protected Request $request; - - public function __construct() - { - $this->request = new Request(); + public function __construct( + private readonly Request $request + ) { } public function validatePost(): array @@ -24,8 +22,8 @@ public function validatePost(): array $requiredFields = ['name', 'shortDescription', 'type']; foreach ($requiredFields as $field) { - if (!isset($data[$field])) { - throw new Exception(ucfirst($field).' is required'); + if (false === isset($data[$field])) { + throw new FieldRequiredException(ucfirst($field)); } } diff --git a/api/mapas/app/src/Request/SealRequest.php b/api/mapas/app/src/Request/SealRequest.php index 2c87eb25a3..06ba03def8 100644 --- a/api/mapas/app/src/Request/SealRequest.php +++ b/api/mapas/app/src/Request/SealRequest.php @@ -7,20 +7,16 @@ use App\DTO\SealDto; use App\Exception\ValidatorException; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; -use Symfony\Component\Validator\Validation; +use Symfony\Component\Serializer\SerializerInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; class SealRequest { - protected Request $request; - - protected Serializer $serializer; - - public function __construct() - { - $this->request = new Request(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly Request $request, + private readonly ValidatorInterface $validator, + private readonly SerializerInterface $serializer + ) { } public function validatePost(): array @@ -32,9 +28,7 @@ public function validatePost(): array $seal = $this->serializer->denormalize($data, SealDto::class); - $validation = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); - - $violations = $validation->validate($seal, groups: ['post']); + $violations = $this->validator->validate($seal, groups: ['post']); if (0 < count($violations)) { throw new ValidatorException(violations: $violations); @@ -52,9 +46,7 @@ public function validatePatch(): array $seal = $this->serializer->denormalize($data, SealDto::class); - $validation = Validation::createValidatorBuilder()->enableAttributeMapping()->getValidator(); - - $violations = $validation->validate($seal, groups: ['patch']); + $violations = $this->validator->validate($seal, groups: ['patch']); if (0 < count($violations)) { throw new ValidatorException(violations: $violations); diff --git a/api/mapas/app/src/Request/SpaceRequest.php b/api/mapas/app/src/Request/SpaceRequest.php index c99fdecc8c..9a8ca20ddb 100644 --- a/api/mapas/app/src/Request/SpaceRequest.php +++ b/api/mapas/app/src/Request/SpaceRequest.php @@ -4,16 +4,15 @@ namespace App\Request; -use Exception; +use App\Exception\FieldRequiredException; +use App\Exception\InvalidRequestException; use Symfony\Component\HttpFoundation\Request; class SpaceRequest { - protected Request $request; - - public function __construct() - { - $this->request = new Request(); + public function __construct( + private Request $request + ) { } public function validatePost(): array @@ -24,13 +23,13 @@ public function validatePost(): array $requiredFields = ['name', 'terms', 'type', 'shortDescription']; foreach ($requiredFields as $field) { - if (!isset($data[$field])) { - throw new Exception(ucfirst($field).' is required'); + if (false === isset($data[$field])) { + throw new FieldRequiredException(ucfirst($field)); } } - if (!is_array($data['terms']) || !is_array($data['terms']['area'])) { - throw new Exception('the terms field must be an object with a property "area" which is an array'); + if (false === is_array($data['terms']) || false === is_array($data['terms']['area'])) { + throw new InvalidRequestException('the terms field must be an object with a property "area" which is an array'); } return $data; diff --git a/api/mapas/app/src/Request/TermRequest.php b/api/mapas/app/src/Request/TermRequest.php new file mode 100644 index 0000000000..4b4472459b --- /dev/null +++ b/api/mapas/app/src/Request/TermRequest.php @@ -0,0 +1,53 @@ +validateTerm(self::GROUP_POST); + } + + public function validatePatch(): array + { + return $this->validateTerm(self::GROUP_PATCH); + } + + public function validateTerm(string $validatorGroup): array + { + $data = json_decode( + json: $this->request->getContent(), + associative: true + ); + + $term = $this->serializer->denormalize($data, TermDto::class); + + $violations = $this->validator->validate($term, groups: [$validatorGroup]); + + if (0 < count($violations)) { + throw new ValidatorException(violations: $violations); + } + + return $data; + } +} diff --git a/api/mapas/app/src/Service/AgentService.php b/api/mapas/app/src/Service/AgentService.php index a0e34dd5fd..88b8f366a7 100644 --- a/api/mapas/app/src/Service/AgentService.php +++ b/api/mapas/app/src/Service/AgentService.php @@ -4,24 +4,19 @@ namespace App\Service; -use App\Enum\EntityStatusEnum; -use App\Exception\ResourceNotFoundException; -use App\Repository\AgentRepository; +use App\Repository\Interface\AgentRepositoryInterface; +use App\Service\Interface\AgentServiceInterface; use MapasCulturais\Entities\Agent; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -class AgentService +class AgentService implements AgentServiceInterface { - protected AgentRepository $repository; - private SerializerInterface $serializer; public const FILE_TYPES = '/src/conf/agent-types.php'; - public function __construct() - { - $this->repository = new AgentRepository(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly AgentRepositoryInterface $repository, + private readonly SerializerInterface $serializer + ) { } public function getTypes(): array @@ -35,17 +30,10 @@ public function getTypes(): array ); } - /** - * @throws ResourceNotFoundException - */ public function update(int $id, object $data): Agent { $agentFromDB = $this->repository->find($id); - if (null === $agentFromDB || EntityStatusEnum::TRASH->getValue() === $agentFromDB->status) { - throw new ResourceNotFoundException('Agent not found'); - } - $agentUpdated = $this->serializer->denormalize( data: $data, type: Agent::class, @@ -58,7 +46,7 @@ public function update(int $id, object $data): Agent return $agentUpdated; } - public function create($data): Agent + public function create(mixed $data): Agent { $agent = new Agent(); $agent->setName($data->name); @@ -72,17 +60,9 @@ public function create($data): Agent return $agent; } - /** - * @throws ResourceNotFoundException - */ - public function discard(int $id): void + public function removeById(int $id): void { $agent = $this->repository->find($id); - - if (null === $agent || EntityStatusEnum::TRASH->getValue() === $agent->status) { - throw new ResourceNotFoundException('Agent not found'); - } - - $this->repository->softDelete($agent); + $this->repository->remove($agent); } } diff --git a/api/mapas/app/src/Service/EventService.php b/api/mapas/app/src/Service/EventService.php index 4c8500a9ee..7258729b96 100644 --- a/api/mapas/app/src/Service/EventService.php +++ b/api/mapas/app/src/Service/EventService.php @@ -4,28 +4,19 @@ namespace App\Service; -use App\Enum\EntityStatusEnum; -use App\Repository\EventRepository; -use App\Request\EventRequest; +use App\Repository\Interface\EventRepositoryInterface; +use App\Service\Interface\EventServiceInterface; use MapasCulturais\Entities\Event; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -class EventService +class EventService implements EventServiceInterface { - protected EventRepository $repository; - protected EventRequest $eventRequest; - public const FILE_TYPES = '/src/conf/event-types.php'; - private SerializerInterface $serializer; - public function __construct() - { - $this->repository = new EventRepository(); - $this->eventRequest = new EventRequest(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly EventRepositoryInterface $repository, + private readonly SerializerInterface $serializer + ) { } public function getTypes(): array @@ -35,7 +26,7 @@ public function getTypes(): array return array_map(fn ($key, $item) => ['id' => $key, 'name' => $item['name']], array_keys($typesFromConf), $typesFromConf); } - public function create($data): Event + public function create(mixed $data): Event { $event = new Event(); @@ -53,10 +44,6 @@ public function update(int $id, object $data): Event { $eventFromDB = $this->repository->find($id); - if (null === $eventFromDB || EntityStatusEnum::TRASH->getValue() === $eventFromDB->status) { - throw new ResourceNotFoundException('Event not found or already deleted.'); - } - $eventUpdated = $this->serializer->denormalize( data: $data, type: Event::class, @@ -68,4 +55,10 @@ public function update(int $id, object $data): Event return $eventUpdated; } + + public function removeById(int $id): void + { + $event = $this->repository->find($id); + $this->repository->remove($event); + } } diff --git a/api/mapas/app/src/Service/Interface/AgentServiceInterface.php b/api/mapas/app/src/Service/Interface/AgentServiceInterface.php new file mode 100644 index 0000000000..7f618621eb --- /dev/null +++ b/api/mapas/app/src/Service/Interface/AgentServiceInterface.php @@ -0,0 +1,18 @@ +repository = new OpportunityRepository(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly OpportunityRepositoryInterface $repository, + private readonly SerializerInterface $serializer + ) { } public function getTypes(): array { - $typesFromConf = (require dirname(__DIR__, 3).'/src/conf/opportunity-types.php')['items'] ?? []; + $typesFromConf = (require dirname(__DIR__, 3).self::FILE_TYPES)['items'] ?? []; return array_map( fn ($key, $item) => ['id' => $key, 'name' => $item['name']], @@ -40,11 +33,6 @@ public function getTypes(): array public function update(int $id, object $data): Opportunity { $opportunityFromDB = $this->repository->find($id); - - if (null === $opportunityFromDB || EntityStatusEnum::TRASH->getValue() === $opportunityFromDB->status) { - throw new ResourceNotFoundException('Opportunity not found'); - } - $opportunityUpdated = $this->serializer->denormalize( data: $data, type: Opportunity::class, @@ -57,7 +45,7 @@ public function update(int $id, object $data): Opportunity return $opportunityUpdated; } - public function create($data): Opportunity + public function create(mixed $data): Opportunity { $opportunity = new Opportunity(); @@ -65,19 +53,19 @@ public function create($data): Opportunity $opportunity->setName($data['name']); $opportunity->terms['area'] = $data['terms']['area']; - if (isset($data['project'])) { + if (true === isset($data['project'])) { $opportunity->setObjectType("MapasCulturais\Entities\Project"); $opportunity->setProject($data['project']); } - if (isset($data['event'])) { + if (true === isset($data['event'])) { $opportunity->setObjectType("MapasCulturais\Entities\Event"); $opportunity->setEvent($data['event']); } - if (isset($data['space'])) { + if (true === isset($data['space'])) { $opportunity->setObjectType("MapasCulturais\Entities\Space"); $opportunity->setSpace($data['space']); } - if (isset($data['agent'])) { + if (true === isset($data['agent'])) { $opportunity->setObjectType("MapasCulturais\Entities\Agent"); $opportunity->setAgent($data['agent']); } @@ -86,4 +74,10 @@ public function create($data): Opportunity return $opportunity; } + + public function removeById(int $id): void + { + $opportunity = $this->repository->find($id); + $this->repository->remove($opportunity); + } } diff --git a/api/mapas/app/src/Service/ProjectService.php b/api/mapas/app/src/Service/ProjectService.php index 1c38077f04..115ad851a1 100644 --- a/api/mapas/app/src/Service/ProjectService.php +++ b/api/mapas/app/src/Service/ProjectService.php @@ -4,27 +4,32 @@ namespace App\Service; -use App\Enum\EntityStatusEnum; -use App\Repository\ProjectRepository; +use App\Exception\FieldRequiredException; +use App\Repository\Interface\ProjectRepositoryInterface; +use App\Service\Interface\ProjectServiceInterface; use MapasCulturais\Entities\Project; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -class ProjectService +class ProjectService implements ProjectServiceInterface { - protected ProjectRepository $projectRepository; - private SerializerInterface $serializer; - - public function __construct() - { - $this->projectRepository = new ProjectRepository(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly ProjectRepositoryInterface $projectRepository, + private readonly SerializerInterface $serializer + ) { } - public function create($data): Project + public function create(mixed $data): Project { + if (true === empty($data->name)) { + throw new FieldRequiredException('The name field is required.'); + } + if (true === empty($data->shortDescription)) { + throw new FieldRequiredException('The shortDescription field is required.'); + } + if (true === empty($data->type)) { + throw new FieldRequiredException('The type field is required.'); + } + $project = new Project(); $project->setName($data->name); $project->setShortDescription($data->shortDescription); @@ -39,8 +44,8 @@ public function update(int $id, object $data): Project { $projectFromDB = $this->projectRepository->find($id); - if (null === $projectFromDB || EntityStatusEnum::TRASH->getValue() === $projectFromDB->status) { - throw new ResourceNotFoundException('Project not found or already deleted.'); + if (true === empty($data->name)) { + throw new FieldRequiredException('name'); } $projectUpdated = $this->serializer->denormalize( @@ -48,6 +53,7 @@ public function update(int $id, object $data): Project type: Project::class, context: ['object_to_populate' => $projectFromDB] ); + $projectUpdated->saveTerms(); $projectUpdated->saveMetadata(); @@ -56,17 +62,9 @@ public function update(int $id, object $data): Project return $projectUpdated; } - /** - * @throws ResourceNotFoundException - */ - public function discard(int $id): void + public function removeById(int $id): void { $project = $this->projectRepository->find($id); - - if (null === $project || EntityStatusEnum::TRASH->getValue() === $project->status) { - throw new ResourceNotFoundException('Project not found or already deleted.'); - } - - $this->$project->softDelete($project); + $this->$project->remove($project); } } diff --git a/api/mapas/app/src/Service/SealService.php b/api/mapas/app/src/Service/SealService.php index 2cf1ff2583..edba981a8f 100644 --- a/api/mapas/app/src/Service/SealService.php +++ b/api/mapas/app/src/Service/SealService.php @@ -4,32 +4,22 @@ namespace App\Service; -use App\Enum\EntityStatusEnum; -use App\Exception\ResourceNotFoundException; -use App\Repository\AgentRepository; -use App\Repository\ProjectRepository; -use App\Repository\SealRepository; +use App\Repository\Interface\AgentRepositoryInterface; +use App\Repository\Interface\SealRepositoryInterface; +use App\Service\Interface\SealServiceInterface; use MapasCulturais\Entities\Seal; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -class SealService extends AbstractService +class SealService extends AbstractService implements SealServiceInterface { - private SealRepository $sealRepository; - private AgentRepository $agentRepository; - protected ProjectRepository $projectRepository; - private SerializerInterface $serializer; - - public function __construct() - { - $this->projectRepository = new ProjectRepository(); - $this->serializer = new Serializer([new ObjectNormalizer()]); - $this->sealRepository = new SealRepository(); - $this->agentRepository = new AgentRepository(); + public function __construct( + private readonly AgentRepositoryInterface $agentRepository, + private readonly SealRepositoryInterface $sealRepository, + private readonly SerializerInterface $serializer + ) { } - public function create(array $data): mixed + public function create(mixed $data): Seal { $seal = $this->serializer->denormalize($data, Seal::class); $this->setProperty($seal, 'owner', $this->agentRepository->find(1)); @@ -38,33 +28,16 @@ public function create(array $data): mixed return $seal; } - /** - * @throws ResourceNotFoundException - */ - public function delete(int $id): true + public function removeById(int $id): void { $seal = $this->sealRepository->find($id); - - if (null === $seal || EntityStatusEnum::TRASH->getValue() === $seal->status) { - throw new ResourceNotFoundException('Seal not found'); - } - - $this->sealRepository->softDelete($seal); - - return true; + $this->sealRepository->remove($seal); } - /** - * @throws ResourceNotFoundException - */ public function update(int $id, object $data): Seal { $sealFromDB = $this->sealRepository->find($id); - if (null === $sealFromDB || EntityStatusEnum::TRASH->getValue() === $sealFromDB->status) { - throw new ResourceNotFoundException('Seal not found'); - } - $sealUpdated = $this->serializer->denormalize( data: $data, type: Seal::class, diff --git a/api/mapas/app/src/Service/SpaceService.php b/api/mapas/app/src/Service/SpaceService.php index 216d107107..1ccfc80f4b 100644 --- a/api/mapas/app/src/Service/SpaceService.php +++ b/api/mapas/app/src/Service/SpaceService.php @@ -4,25 +4,19 @@ namespace App\Service; -use App\Enum\EntityStatusEnum; -use App\Exception\ResourceNotFoundException; -use App\Repository\SpaceRepository; +use App\Repository\Interface\SpaceRepositoryInterface; +use App\Service\Interface\SpaceServiceInterface; use MapasCulturais\Entities\Space; -use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; -use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\SerializerInterface; -class SpaceService +class SpaceService implements SpaceServiceInterface { - protected SpaceRepository $repository; - private SerializerInterface $serializer; - public const FILE_TYPES = '/src/conf/space-types.php'; - public function __construct() - { - $this->repository = new SpaceRepository(); - $this->serializer = new Serializer([new ObjectNormalizer()]); + public function __construct( + private readonly SerializerInterface $serializer, + private readonly SpaceRepositoryInterface $repository + ) { } public function getTypes(): array @@ -36,7 +30,7 @@ public function getTypes(): array ); } - public function create($data): Space + public function create(mixed $data): Space { $space = new Space(); @@ -51,21 +45,19 @@ public function create($data): Space return $space; } - /** - * @throws ResourceNotFoundException - */ - public function update($id, $data): Space + public function update(int $id, object $data): Space { $spaceFromDB = $this->repository->find($id); - - if (null === $spaceFromDB || EntityStatusEnum::TRASH->getValue() === $spaceFromDB->status) { - throw new ResourceNotFoundException('Space not found'); - } - - $spaceUpdated = $this->serializer->denormalize($data, Space::class, null, ['object_to_populate' => $spaceFromDB]); + $spaceUpdated = $this->serializer->denormalize($data, Space::class, context: ['object_to_populate' => $spaceFromDB]); $this->repository->save($spaceUpdated); return $spaceUpdated; } + + public function removeById(int $id): void + { + $space = $this->repository->find($id); + $this->repository->remove($space); + } } diff --git a/api/mapas/app/src/Service/TermService.php b/api/mapas/app/src/Service/TermService.php new file mode 100644 index 0000000000..d836d4dcca --- /dev/null +++ b/api/mapas/app/src/Service/TermService.php @@ -0,0 +1,48 @@ +serializer->denormalize($data, Term::class); + $this->termRepository->save($term); + + return $term; + } + + public function update(int $id, array $termData): Term + { + $termFromDB = $this->termRepository->find($id); + + $termUpdated = $this->serializer->denormalize( + data: $termData, + type: Term::class, + context: ['object_to_populate' => $termFromDB] + ); + + $this->termRepository->save($termUpdated); + + return $termUpdated; + } + + public function removeById(int $id): void + { + $term = $this->termRepository->find($id); + $this->termRepository->remove($term); + } +} diff --git a/api/mapas/app/src/Validator/Constraints/UniqueInEntity.php b/api/mapas/app/src/Validator/Constraints/UniqueInEntity.php new file mode 100644 index 0000000000..74ff494ab2 --- /dev/null +++ b/api/mapas/app/src/Validator/Constraints/UniqueInEntity.php @@ -0,0 +1,27 @@ +entity = $app->em; + } + + public function validate(mixed $value, Constraint $constraint): void + { + if (false === $constraint instanceof UniqueInEntity) { + throw new UnexpectedTypeException($constraint, UniqueInEntity::class); + } + + if (null === $value || '' === $value) { + return; + } + + $result = $this->entity->getRepository($constraint->entity)->findBy([$constraint->field => $value]); + + if ([] === $result) { + return; + } + + $this->context->buildViolation($constraint->message)->addViolation(); + } +} diff --git a/api/mapas/app/tests/Functional/AgentApiControllerTest.php b/api/mapas/app/tests/Functional/AgentApiControllerTest.php new file mode 100644 index 0000000000..d23846b5b4 --- /dev/null +++ b/api/mapas/app/tests/Functional/AgentApiControllerTest.php @@ -0,0 +1,120 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneAgentShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testGetAgentTypesShouldRetrieveAList(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/types'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetAgentOpportunitiesShouldRetrieveAList(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1/opportunities'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testCreateAgentShouldCreateAnAgent(): void + { + $this->markTestSkipped(); + $agentTestFixtures = AgentTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $agentTestFixtures->json(), + ]); + + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + + $content = json_decode($response->getContent(), true); + + foreach ($agentTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testDeleteAgentShouldReturnSuccess(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/2'); + + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/2'); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + public function testUpdate(): void + { + $agentTestFixtures = AgentTestFixtures::partial(); + + $url = sprintf(self::BASE_URL.'/%s', AgentFixtures::AGENT_ID_4); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $agentTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($agentTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateNotFoundedResource(): void + { + $agentTestFixtures = AgentTestFixtures::partial(); + + $url = sprintf(self::BASE_URL.'/%s', 1969); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $agentTestFixtures->json(), + ]); + + $error = [ + 'error' => 'The resource was not found.', + ]; + + $content = json_decode($response->getContent(false), true); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsArray($content); + $this->assertEquals($error, $content); + } +} diff --git a/api/mapas/app/tests/Functional/EventApiControllerTest.php b/api/mapas/app/tests/Functional/EventApiControllerTest.php new file mode 100644 index 0000000000..6d8000483c --- /dev/null +++ b/api/mapas/app/tests/Functional/EventApiControllerTest.php @@ -0,0 +1,107 @@ +client->request(Request::METHOD_GET, self::BASE_URL.'/types'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneAgentShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testGetEventsBySpacesShouldRetrieveAList(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_GET, '/api/v2/spaces/4/events'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testPostEventShouldCreateANewEvent(): void + { + $this->markTestSkipped(); + $eventTestFixtures = EventTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $eventTestFixtures->json(), + ]); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testDeleteEventShouldRemoveAnEvent(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testUpdateEventShouldUpdateAnEvent(): void + { + $this->markTestSkipped(); + $eventTestFixtures = EventTestFixtures::partial(); + + $url = sprintf(self::BASE_URL.'/%s', EventFixtures::EVENT_ID_2); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $eventTestFixtures->json(), + ]); + + $this->dump($response); + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($eventTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateNotFoundedEventResource(): void + { + $eventTestFixtures = EventTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', 1024); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $eventTestFixtures->json(), + ]); + + $error = [ + 'error' => 'The resource was not found.', + ]; + + $content = json_decode($response->getContent(false), true); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsArray($content); + $this->assertEquals($error, $content); + } +} diff --git a/api/mapas/app/tests/Functional/OpportunityApiControllerTest.php b/api/mapas/app/tests/Functional/OpportunityApiControllerTest.php new file mode 100644 index 0000000000..37c731bfcc --- /dev/null +++ b/api/mapas/app/tests/Functional/OpportunityApiControllerTest.php @@ -0,0 +1,32 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneOpportunityShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } +} diff --git a/api/mapas/app/tests/Functional/ProjectApiControllerTest.php b/api/mapas/app/tests/Functional/ProjectApiControllerTest.php new file mode 100644 index 0000000000..2093bb70f8 --- /dev/null +++ b/api/mapas/app/tests/Functional/ProjectApiControllerTest.php @@ -0,0 +1,96 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneProjectShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testCreateProjectShouldCreateAProject(): void + { + $projectTestFixtures = ProjectTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $projectTestFixtures->json(), + ]); + + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + + $content = json_decode($response->getContent(), true); + + $this->assertEquals('Project Test', $content['name']); + } + + public function testDeleteProjectShouldReturnSuccess(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/1'); + + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + public function testUpdateProjectShouldUpdateAProject(): void + { + $projectTestFixtures = ProjectTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', ProjectFixtures::PROJECT_ID_2); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $projectTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($projectTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateNotFoundedProjectResource(): void + { + $projectTestFixtures = ProjectTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', 1024); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $projectTestFixtures->json(), + ]); + + $error = [ + 'error' => 'The resource was not found.', + ]; + + $content = json_decode($response->getContent(false), true); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsArray($content); + $this->assertEquals($error, $content); + } +} diff --git a/api/mapas/app/tests/Functional/SealApiControllerTest.php b/api/mapas/app/tests/Functional/SealApiControllerTest.php new file mode 100644 index 0000000000..ab18cba0c0 --- /dev/null +++ b/api/mapas/app/tests/Functional/SealApiControllerTest.php @@ -0,0 +1,100 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneSealShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/3'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testCreateSealShouldReturnCreatedSeal(): void + { + $this->markTestSkipped(); + $sealTestFixtures = SealTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $sealTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($sealTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testDeleteSealShouldReturnSuccess(): void + { + $this->markTestSkipped(); + $sealId = SealFixtures::SEAL_ID_6; + + $response = $this->client->request(Request::METHOD_DELETE, sprintf(self::BASE_URL.'/%s', $sealId)); + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + + $response = $this->client->request(Request::METHOD_GET, sprintf(self::BASE_URL.'/%s', $sealId)); + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + } + + public function testUpdate(): void + { + $this->markTestSkipped(); + $sealTestFixtures = SealTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', SealFixtures::SEAL_ID_3); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $sealTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($sealTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateNotFoundedResource(): void + { + $sealTestFixtures = SealTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', 80); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $sealTestFixtures->json(), + ]); + + $error = [ + 'error' => 'The resource was not found.', + ]; + + $content = json_decode($response->getContent(false), true); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsArray($content); + $this->assertEquals($error, $content); + } +} diff --git a/api/mapas/app/tests/Functional/SpaceApiControllerTest.php b/api/mapas/app/tests/Functional/SpaceApiControllerTest.php new file mode 100644 index 0000000000..a9a619ac3b --- /dev/null +++ b/api/mapas/app/tests/Functional/SpaceApiControllerTest.php @@ -0,0 +1,99 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + } + + public function testGetOneSpaceShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + } + + public function testCreateSpaceShouldReturnCreatedSpace(): void + { + $this->markTestSkipped(); + $spaceTestFixtures = SpaceTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $spaceTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($spaceTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testDeleteSpaceShouldReturnSuccess(): void + { + $this->markTestSkipped(); + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/1'); + + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + public function testUpdate(): void + { + $this->markTestSkipped(); + $spaceTestFixtures = SpaceTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', SpaceFixtures::SPACE_ID_3); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $spaceTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertIsArray($content); + foreach ($spaceTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateNotFoundedResource(): void + { + $spaceTestFixtures = SpaceTestFixtures::partial(); + $url = sprintf(self::BASE_URL.'/%s', 1024); + + $response = $this->client->request(Request::METHOD_PATCH, $url, [ + 'body' => $spaceTestFixtures->json(), + ]); + + $error = [ + 'error' => 'The resource was not found.', + ]; + + $content = json_decode($response->getContent(false), true); + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsArray($content); + $this->assertEquals($error, $content); + } +} diff --git a/api/mapas/app/tests/Functional/TermApiControllerTest.php b/api/mapas/app/tests/Functional/TermApiControllerTest.php new file mode 100644 index 0000000000..ac0c173855 --- /dev/null +++ b/api/mapas/app/tests/Functional/TermApiControllerTest.php @@ -0,0 +1,183 @@ +client->request(Request::METHOD_GET, self::BASE_URL); + $content = json_decode($response->getContent(), true); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsArray($content); + + $this->assertArrayHasKey('id', $content[0]); + $this->assertArrayHasKey('taxonomy', $content[0]); + + $this->assertEquals(1, $content[0]['id']); + $this->assertEquals('area', $content[0]['taxonomy']); + } + + public function testGetOneTermShouldRetrieveAObject(): void + { + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/1'); + $content = json_decode($response->getContent()); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsObject($content); + + $this->assertObjectHasProperty('@entityType', $content); + $this->assertEquals('term', $content->{'@entityType'}); + } + + public function testGetOneTermShouldReturnNotFound(): void + { + $nonExistentId = 99999999; + $response = $this->client->request(Request::METHOD_GET, self::BASE_URL.'/'.$nonExistentId); + + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + public function testCreateTermShouldCreateANewTerm(): void + { + $termTestFixtures = TermTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'body' => $termTestFixtures->json(), + ]); + + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + + $content = json_decode($response->getContent(), true); + + foreach ($termTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testDeleteTermShouldReturnNoContent(): void + { + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/1'); + + $this->assertEquals(Response::HTTP_NO_CONTENT, $response->getStatusCode()); + } + + public function testDeletedTermShouldReturnNotFound(): void + { + $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/202'); + + $response = $this->client->request(Request::METHOD_DELETE, self::BASE_URL.'/202'); + + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + public function testTermUpdateShouldReturnUpdatedTerm(): void + { + $termTestFixtures = TermTestFixtures::partial(); + + $response = $this->client->request(Request::METHOD_PATCH, self::BASE_URL.'/2', [ + 'body' => $termTestFixtures->json(), + ]); + + $content = json_decode($response->getContent(), true); + + $this->assertIsArray($content); + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + foreach ($termTestFixtures->toArray() as $key => $value) { + $this->assertEquals($value, $content[$key]); + } + } + + public function testUpdateATermThatNotExists(): void + { + $response = $this->client->request(Request::METHOD_PATCH, self::BASE_URL.'/999', [ + 'body' => json_encode([ + 'taxonomy' => 'update', + 'term' => 'update', + 'description' => 'update', + ]), + ]); + + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + } + + /** + * @dataProvider termDataProvider + */ + public function testTermValidations(array $termData, array $expectedMessages): void + { + $this->validateTerm($termData, $expectedMessages); + } + + private function validateTerm(array $termData, array $expectedMessages): void + { + $response = $this->client->request(Request::METHOD_POST, self::BASE_URL, [ + 'json' => $termData, + ]); + + $statusCode = $response->getStatusCode(); + $responseData = $response->toArray(false); + + $this->assertEquals(Response::HTTP_BAD_REQUEST, $statusCode); + $this->assertEquals('The provided data violates one or more constraints.', $responseData['error']); + + $actualMessages = array_map(function ($fieldError) { + return $fieldError['message']; + }, $responseData['fields']); + + $this->assertEquals($expectedMessages, $actualMessages); + } + + public static function termDataProvider(): array + { + return [ + 'blank fields' => [ + 'termData' => [ + 'taxonomy' => '', + 'term' => '', + 'description' => '', + ], + 'expectedMessages' => [ + 'This value should not be blank.', + 'This value is too short. It should have 2 characters or more.', + 'This value should not be blank.', + 'This value is too short. It should have 2 characters or more.', + 'This value should not be blank.', + 'This value is too short. It should have 2 characters or more.', + ], + ], + 'invalid type fields' => [ + 'termData' => [ + 'taxonomy' => 123, + 'term' => 123, + 'description' => 123, + ], + 'expectedMessages' => [ + 'This value should be of type string.', + 'This value should be of type string.', + 'This value should be of type string.', + ], + ], + 'term already exists' => [ + 'termData' => [ + 'taxonomy' => 'existing_taxonomy', + 'term' => 'Arqueologia', + 'description' => 'existing_description', + ], + 'expectedMessages' => [ + 'This value is already used.', + ], + ], + ]; + } +} diff --git a/api/mapas/app/tests/Functional/WelcomeApiControllerTest.php b/api/mapas/app/tests/Functional/WelcomeApiControllerTest.php new file mode 100644 index 0000000000..edab0ae75b --- /dev/null +++ b/api/mapas/app/tests/Functional/WelcomeApiControllerTest.php @@ -0,0 +1,44 @@ +client->request('GET', '/api'); + $content = json_decode($response->getContent()); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('MapaCultural', $content->API); + } + + public function testMethodDELETEFromAPI(): void + { + $response = $this->client->request('DELETE', '/api/v2'); + $content = json_decode($response->getContent()); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('MapaCultural - Test DELETE', $content->API); + } + + public function testmethodPOSTFromAPI(): void + { + $response = $this->client->request('POST', '/api/v2'); + $content = json_decode($response->getContent()); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertEquals('MapaCultural - Test POST', $content->API); + } + + public function testErrorResponseWhenMethodIsNotAllowed(): void + { + $response = $this->client->request('POST', '/api'); + + $this->assertEquals(405, $response->getStatusCode()); + } +} diff --git a/api/mapas/app/tests/fixtures/TermTestFixtures.php b/api/mapas/app/tests/fixtures/TermTestFixtures.php new file mode 100644 index 0000000000..5cdddca711 --- /dev/null +++ b/api/mapas/app/tests/fixtures/TermTestFixtures.php @@ -0,0 +1,22 @@ + 'category', + 'term' => 'Test Term', + 'description' => 'Test Term Description', + ]); + } + + public static function complete(): array + { + return []; + } +}