From 7ba4deaafae56d96519fb80c4bcf275999742fb0 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 18 Jul 2024 08:24:56 +0200 Subject: [PATCH 1/9] refs #512. API Crud views --- composer.json | 1 + composer.lock | 97 ++++++++++++++++++- config/api_platform/View.yaml | 30 ++++++ config/packages/security.yaml | 2 +- config/packages/translation.yaml | 8 ++ config/services.yaml | 11 +++ migrations/Version20240717094811.php | 31 ++++++ src/Dto/Input/ClientInput.php | 53 ++++++---- src/Dto/Input/ViewInput.php | 49 ++++++++++ src/Dto/Output/ViewOutput.php | 29 ++++++ src/Entity/View.php | 50 ++++++++++ src/EventListener/LocaleSubscriber.php | 45 +++++++++ src/Factory/ViewFactory.php | 61 ++++++++++++ src/Repository/ViewRepository.php | 18 ++++ src/State/Processor/ViewProcessor.php | 68 +++++++++++++ src/State/Provider/ViewProvider.php | 71 ++++++++++++++ symfony.lock | 13 +++ tests/Functional/ViewTest.php | 128 +++++++++++++++++++++++++ translations/messages.es.yaml | 0 translations/validators.en.yaml | 13 +++ translations/validators.es.yaml | 13 +++ 21 files changed, 773 insertions(+), 18 deletions(-) create mode 100644 config/api_platform/View.yaml create mode 100644 config/packages/translation.yaml create mode 100644 migrations/Version20240717094811.php create mode 100644 src/Dto/Input/ViewInput.php create mode 100644 src/Dto/Output/ViewOutput.php create mode 100644 src/Entity/View.php create mode 100644 src/EventListener/LocaleSubscriber.php create mode 100644 src/Factory/ViewFactory.php create mode 100644 src/Repository/ViewRepository.php create mode 100644 src/State/Processor/ViewProcessor.php create mode 100644 src/State/Provider/ViewProvider.php create mode 100644 tests/Functional/ViewTest.php create mode 100644 translations/messages.es.yaml create mode 100644 translations/validators.en.yaml create mode 100644 translations/validators.es.yaml diff --git a/composer.json b/composer.json index 4a2dab4..580ef6b 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "symfony/runtime": "6.4.*", "symfony/security-bundle": "6.4.*", "symfony/serializer": "6.4.*", + "symfony/translation": "6.4.*", "symfony/twig-bundle": "6.4.*", "symfony/validator": "6.4.*", "symfony/yaml": "6.4.*" diff --git a/composer.lock b/composer.lock index f49d68c..e756ea9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e953b0066fb773aaf6b3835086280c86", + "content-hash": "bf1165324e27bddd1a412f25e438fc4c", "packages": [ { "name": "api-platform/core", @@ -6061,6 +6061,101 @@ ], "time": "2024-05-31T14:49:08+00:00" }, + { + "name": "symfony/translation", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/a002933b13989fc4bd0b58e04bf7eec5210e438a", + "reference": "a002933b13989fc4bd0b58e04bf7eec5210e438a", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^2.5|^3.0" + }, + "conflict": { + "symfony/config": "<5.4", + "symfony/console": "<5.4", + "symfony/dependency-injection": "<5.4", + "symfony/http-client-contracts": "<2.5", + "symfony/http-kernel": "<5.4", + "symfony/service-contracts": "<2.5", + "symfony/twig-bundle": "<5.4", + "symfony/yaml": "<5.4" + }, + "provide": { + "symfony/translation-implementation": "2.3|3.0" + }, + "require-dev": { + "nikic/php-parser": "^4.18|^5.0", + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/http-client-contracts": "^2.5|^3.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/polyfill-intl-icu": "^1.21", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/yaml": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools to internationalize your application", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/translation/tree/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, { "name": "symfony/translation-contracts", "version": "v3.5.0", diff --git a/config/api_platform/View.yaml b/config/api_platform/View.yaml new file mode 100644 index 0000000..d6206ee --- /dev/null +++ b/config/api_platform/View.yaml @@ -0,0 +1,30 @@ +resources: + App\Entity\View: + processor: App\State\Processor\ViewProcessor + input: App\Dto\Input\ViewInput + output: App\Dto\Output\ViewOutput + normalizationContext: + groups: ['default', 'view:read'] + denormalizationContext: + groups: ['view:write'] + operations: + ApiPlatform\Metadata\GetCollection: + provider: App\State\Provider\ViewProvider + filters: + - 'api_platform.filter.view.order' + - 'api_platform.filter.view.search' + ApiPlatform\Metadata\Get: + provider: App\State\Provider\ViewProvider + ApiPlatform\Metadata\Put: + provider: App\State\Provider\ViewProvider + ApiPlatform\Metadata\Patch: + provider: App\State\Provider\ViewProvider + ApiPlatform\Metadata\Post: ~ + ApiPlatform\Metadata\Delete: ~ + +properties: + App\Entity\View: + id: + identifier: false + uuid: + identifier: true \ No newline at end of file diff --git a/config/packages/security.yaml b/config/packages/security.yaml index cf97c8d..5e2315b 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -11,7 +11,7 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false main: - stateless: true + stateless: false provider: app_user_provider entry_point: jwt json_login: diff --git a/config/packages/translation.yaml b/config/packages/translation.yaml new file mode 100644 index 0000000..bf1213d --- /dev/null +++ b/config/packages/translation.yaml @@ -0,0 +1,8 @@ +framework: + default_locale: es + translator: + default_path: '%kernel.project_dir%/translations' + fallbacks: + - en + - es + providers: diff --git a/config/services.yaml b/config/services.yaml index a26d706..beee276 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -21,6 +21,12 @@ services: - { name: api_platform.doctrine.orm.query_extension.collection } - { name: api_platform.doctrine.orm.query_extension.item } + App\EventListener\LocaleSubscriber: + arguments: + $defaultLocale: '%kernel.default_locale%' + tags: + - { name: kernel.event_subscriber } + App\OpenApi\OpenApiFactory: decorates: 'api_platform.openapi.factory' arguments: [ '@App\OpenApi\OpenApiFactory.inner' ] @@ -90,3 +96,8 @@ services: bind: $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' $itemProvider: '@api_platform.doctrine.orm.state.item_provider' + + App\State\Provider\ViewProvider: + bind: + $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' + $itemProvider: '@api_platform.doctrine.orm.state.item_provider' diff --git a/migrations/Version20240717094811.php b/migrations/Version20240717094811.php new file mode 100644 index 0000000..c1eb1f2 --- /dev/null +++ b/migrations/Version20240717094811.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE view (id INT AUTO_INCREMENT NOT NULL, uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', migration_id VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, favourite TINYINT(1) NOT NULL, filters JSON DEFAULT NULL COMMENT \'(DC2Type:json)\', name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_FEFDAB8ED17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE view'); + } +} diff --git a/src/Dto/Input/ClientInput.php b/src/Dto/Input/ClientInput.php index 2b124dd..5f45ffc 100644 --- a/src/Dto/Input/ClientInput.php +++ b/src/Dto/Input/ClientInput.php @@ -12,44 +12,67 @@ use Symfony\Component\Validator\Constraints as Assert; final class ClientInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.client.name.not_blank')] #[Groups(['client:write'])] - #[ApiProperty(description: 'The name of the client', example: "Client 1")] + #[ApiProperty( + description: 'descriptions.client.name', + example: "examples.client.name", + )] public ?string $name = null; #[Groups(['client:write'])] - #[ApiProperty(description: 'The serial number of the client', example: "123456")] + #[ApiProperty( + description: 'descriptions.client.serial_number', + example: "examples.client.serial_number" + )] public ?string $serialNumber = null; #[Groups(['client:write'])] - #[ApiProperty(description: 'The network interface of the client', example: "eth0")] + #[ApiProperty( + description: 'descriptions.client.network_interface', + example: "examples.client.network_interface" + )] public ?string $netiface = null; #[Groups(['client:write'])] - #[ApiProperty(description: 'The network driver of the client', example: "e1000e")] + #[ApiProperty( + description: 'descriptions.client.network_driver', + example: "examples.client.network_driver" + )] public ?string $netDriver = null; #[Groups(['client:write'])] - #[ApiProperty(description: 'The MAC address of the client', example: "00:11:22:33:44:55")] + #[ApiProperty( + description: 'descriptions.client.mac_address', + example: "examples.client.mac_address" + )] public ?string $mac = null; #[Groups(['client:write'])] - #[Assert\Ip] - #[ApiProperty(description: 'The IP address of the client', example: "127.0.0.1")] + #[Assert\Ip(message: 'validators.ip_address.invalid')] + #[ApiProperty( + description: 'descriptions.client.ip_address', + example: "examples.client.ip_address" + )] public ?string $ip = null; - #[Groups(['client:write'])] - public ?string $status = null; - - #[Assert\NotNull] + #[Assert\NotNull(message: 'validators.organizational_unit.not_null')] #[Groups(['client:write', 'client:patch'])] - #[ApiProperty(description: 'The organizational unit of the client')] + #[ApiProperty( + description: 'descriptions.client.organizational_unit' + )] public ?OrganizationalUnitOutput $organizationalUnit = null; #[Groups(['client:write'])] + #[ApiProperty( + description: 'descriptions.client.menu' + )] public ?MenuOutput $menu = null; #[Groups(['client:write'])] + #[ApiProperty( + description: 'descriptions.client.hardware_profile' + )] public ?HardwareProfileOutput $hardwareProfile = null; public function __construct(?Client $client = null) @@ -65,7 +88,6 @@ final class ClientInput $this->netDriver = $client->getNetDriver(); $this->mac = $client->getMac(); $this->ip = $client->getIp(); - $this->status = $client->getStatus(); if ($client->getMenu()) { $this->menu = new MenuOutput($client->getMenu()); @@ -89,10 +111,9 @@ final class ClientInput $client->setNetDriver($this->netDriver); $client->setMac($this->mac); $client->setIp($this->ip); - $client->setStatus($this->status); $client->setMenu($this->menu?->getEntity()); $client->setHardwareProfile($this->hardwareProfile?->getEntity()); return $client; } -} \ No newline at end of file +} diff --git a/src/Dto/Input/ViewInput.php b/src/Dto/Input/ViewInput.php new file mode 100644 index 0000000..c3932db --- /dev/null +++ b/src/Dto/Input/ViewInput.php @@ -0,0 +1,49 @@ + "value1"])] + public ?array $filters = null; + + public function __construct(?View $view = null) + { + if (!$view) { + return; + } + + $this->name = $view->getName(); + $this->favourite = $view->isFavourite(); + $this->filters= $view->getFilters(); + } + + public function createOrUpdateEntity(?View $view = null): View + { + if (!$view) { + $view = new View(); + } + + $view->setName($this->name); + $view->setFavourite($this->favourite); + $view->setFilters($this->filters); + + return $view; + } + +} \ No newline at end of file diff --git a/src/Dto/Output/ViewOutput.php b/src/Dto/Output/ViewOutput.php new file mode 100644 index 0000000..e2f17bb --- /dev/null +++ b/src/Dto/Output/ViewOutput.php @@ -0,0 +1,29 @@ +name = $view->getName(); + $this->favorite = $view->isFavourite(); + $this->filters = $view->getFilters(); + } +} \ No newline at end of file diff --git a/src/Entity/View.php b/src/Entity/View.php new file mode 100644 index 0000000..4df348a --- /dev/null +++ b/src/Entity/View.php @@ -0,0 +1,50 @@ +name = $name; + + return $this; + } + + public function isFavourite(): ?bool + { + return $this->favourite; + } + + public function setFavourite(bool $favourite): static + { + $this->favourite = $favourite; + + return $this; + } + + public function getFilters(): ?array + { + return $this->filters; + } + + public function setFilters(?array $filters): static + { + $this->filters = $filters; + + return $this; + } +} diff --git a/src/EventListener/LocaleSubscriber.php b/src/EventListener/LocaleSubscriber.php new file mode 100644 index 0000000..4269f8a --- /dev/null +++ b/src/EventListener/LocaleSubscriber.php @@ -0,0 +1,45 @@ +defaultLocale = $defaultLocale; + $this->requestStack = $requestStack; + } + + public function onKernelRequest(RequestEvent $event): void + { + $request = $event->getRequest(); + $locale = $request->getSession()->get('_locale', $this->defaultLocale); + + if ($request->attributes->get('_locale')) { + $locale = $request->attributes->get('_locale'); + } + + $localeFromHeader = $request->getPreferredLanguage(['en', 'es']); + if ($localeFromHeader) { + $locale = $localeFromHeader; + } + + $request->setLocale($locale); + } + + public static function getSubscribedEvents(): array + { + return [ + KernelEvents::REQUEST => [['onKernelRequest', 20]], + ]; + } +} diff --git a/src/Factory/ViewFactory.php b/src/Factory/ViewFactory.php new file mode 100644 index 0000000..09417c8 --- /dev/null +++ b/src/Factory/ViewFactory.php @@ -0,0 +1,61 @@ + + */ +final class ViewFactory extends ModelFactory +{ + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services + * + * @todo inject services if required + */ + public function __construct() + { + parent::__construct(); + } + + public static function class(): string + { + return View::class; + } + + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories + * + * @todo add your default values here + */ + protected function getDefaults(): array + { + return [ + 'createdAt' => self::faker()->dateTime(), + 'favourite' => self::faker()->boolean(), + 'name' => self::faker()->text(255), + 'updatedAt' => self::faker()->dateTime() + ]; + } + + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization + */ + protected function initialize(): self + { + return $this + // ->afterInstantiate(function(View $view): void {}) + ; + } + + protected static function getClass(): string + { + return View::class; + } +} diff --git a/src/Repository/ViewRepository.php b/src/Repository/ViewRepository.php new file mode 100644 index 0000000..573429a --- /dev/null +++ b/src/Repository/ViewRepository.php @@ -0,0 +1,18 @@ + + */ +class ViewRepository extends AbstractRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, View::class); + } +} diff --git a/src/State/Processor/ViewProcessor.php b/src/State/Processor/ViewProcessor.php new file mode 100644 index 0000000..a9914d5 --- /dev/null +++ b/src/State/Processor/ViewProcessor.php @@ -0,0 +1,68 @@ +processCreateOrUpdate($data, $operation, $uriVariables, $context); + case $operation instanceof Delete: + return $this->processDelete($data, $operation, $uriVariables, $context); + } + } + + /** + * @throws \Exception + */ + private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): ViewOutput + { + if (!($data instanceof ViewInput)) { + throw new \Exception(sprintf('data is not instance of %s', ViewInput::class)); + } + + $entity = null; + if (isset($uriVariables['uuid'])) { + $entity = $this->viewRepository->findOneByUuid($uriVariables['uuid']); + } + + $view = $data->createOrUpdateEntity($entity); + $this->validator->validate($view); + $this->viewRepository->save($view); + + return new ViewOutput($view); + } + + private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null + { + $user = $this->viewRepository->findOneByUuid($uriVariables['uuid']); + $this->viewRepository->delete($user); + + return null; + } +} diff --git a/src/State/Provider/ViewProvider.php b/src/State/Provider/ViewProvider.php new file mode 100644 index 0000000..683ab06 --- /dev/null +++ b/src/State/Provider/ViewProvider.php @@ -0,0 +1,71 @@ +provideCollection($operation, $uriVariables, $context); + case $operation instanceof Patch: + case $operation instanceof Put: + return $this->provideInput($operation, $uriVariables, $context); + case $operation instanceof Get: + return $this->provideItem($operation, $uriVariables, $context); + } + } + + private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object + { + $paginator = $this->collectionProvider->provide($operation, $uriVariables, $context); + + $items = new \ArrayObject(); + foreach ($paginator->getIterator() as $item){ + $items[] = new ViewOutput($item); + } + + return new TraversablePaginator($items, $paginator->getCurrentPage(), $paginator->getItemsPerPage(), $paginator->getTotalItems()); + } + + public function provideItem(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + $item = $this->itemProvider->provide($operation, $uriVariables, $context); + + if (!$item) { + throw new NotFoundHttpException('View not found'); + } + + return new ViewOutput($item); + } + + public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null + { + if (isset($uriVariables['uuid'])) { + $item = $this->itemProvider->provide($operation, $uriVariables, $context); + + return $item !== null ? new ViewInput($item) : null; + } + + return new ViewInput(); + } +} diff --git a/symfony.lock b/symfony.lock index d9f1015..caba1f7 100644 --- a/symfony.lock +++ b/symfony.lock @@ -219,6 +219,19 @@ "config/routes/security.yaml" ] }, + "symfony/translation": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.3", + "ref": "e28e27f53663cc34f0be2837aba18e3a1bef8e7b" + }, + "files": [ + "config/packages/translation.yaml", + "translations/.gitignore" + ] + }, "symfony/twig-bundle": { "version": "6.4", "recipe": { diff --git a/tests/Functional/ViewTest.php b/tests/Functional/ViewTest.php new file mode 100644 index 0000000..434ffa9 --- /dev/null +++ b/tests/Functional/ViewTest.php @@ -0,0 +1,128 @@ + self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + + ViewFactory::createMany(10); + + $this->createClientWithCredentials()->request('GET', '/views'); + $this->assertResponseStatusCodeSame(Response::HTTP_OK); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/contexts/View', + '@id' => '/views', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 10, + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testCreateView(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + + ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true]); + $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_CREATE]); + + $this->createClientWithCredentials()->request('POST', '/views',['json' => [ + 'name' => self::VIEW_CREATE, + 'favourite' => true + ]]); + + $this->assertResponseStatusCodeSame(201); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/contexts/ViewOutput', + '@type' => 'View', + 'name' => self::VIEW_CREATE + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testUpdateView(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + + ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true]); + $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_CREATE]); + + $this->createClientWithCredentials()->request('PUT', $viewIri, ['json' => [ + 'name' => self::VIEW_UPDATE, + 'favourite' => false + ]]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@id' => $viewIri, + 'name' => self::VIEW_UPDATE, + 'favorite' => false + ]); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + public function testDeleteView(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + + ViewFactory::createOne(['name' => self::VIEW_DELETE, 'favourite' => true]); + $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_DELETE]); + + $this->createClientWithCredentials()->request('DELETE', $viewIri); + $this->assertResponseStatusCodeSame(204); + $this->assertNull( + static::getContainer()->get('doctrine')->getRepository(View::class)->findOneBy(['name' => self::VIEW_DELETE]) + ); + } +} \ No newline at end of file diff --git a/translations/messages.es.yaml b/translations/messages.es.yaml new file mode 100644 index 0000000..e69de29 diff --git a/translations/validators.en.yaml b/translations/validators.en.yaml new file mode 100644 index 0000000..e205608 --- /dev/null +++ b/translations/validators.en.yaml @@ -0,0 +1,13 @@ +# messages.en.yaml +validators: + client: + ip_address: + invalid: 'The IP address "{{ value }}" is not valid.' + name: + not_blank: 'The name should not be blank.' + organizational_unit: + not_null: 'The organizational unit should not be null.' + + view: + name: + not_blank: 'The name should not be blank.' \ No newline at end of file diff --git a/translations/validators.es.yaml b/translations/validators.es.yaml new file mode 100644 index 0000000..732c4f0 --- /dev/null +++ b/translations/validators.es.yaml @@ -0,0 +1,13 @@ +# messages.es.yaml +validators: + client: + ip_address: + invalid: 'La dirección IP "{{ value }}" no es válida.' + name: + not_blank: 'El nombre no debería estar vacío.' + organizational_unit: + not_null: 'La unidad organizativa no debería estar vacía.' + + view: + name: + not_blank: 'El nombre no debería estar vacío.' \ No newline at end of file From eb647078cae8b24941526d0439a678cb3d238410 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 18 Jul 2024 09:00:20 +0200 Subject: [PATCH 2/9] refs #427. Added position into Client entity --- migrations/Version20240718064611.php | 31 ++++++++++++++++++++++++++++ src/Dto/Input/ClientInput.php | 13 +++++++----- src/Dto/Output/ClientOutput.php | 5 ++++- src/Entity/Client.php | 15 ++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 migrations/Version20240718064611.php diff --git a/migrations/Version20240718064611.php b/migrations/Version20240718064611.php new file mode 100644 index 0000000..fedb106 --- /dev/null +++ b/migrations/Version20240718064611.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE client ADD position JSON DEFAULT NULL COMMENT \'(DC2Type:json)\''); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE client DROP position'); + } +} diff --git a/src/Dto/Input/ClientInput.php b/src/Dto/Input/ClientInput.php index 2b124dd..df1a9c0 100644 --- a/src/Dto/Input/ClientInput.php +++ b/src/Dto/Input/ClientInput.php @@ -38,9 +38,6 @@ final class ClientInput #[ApiProperty(description: 'The IP address of the client', example: "127.0.0.1")] public ?string $ip = null; - #[Groups(['client:write'])] - public ?string $status = null; - #[Assert\NotNull] #[Groups(['client:write', 'client:patch'])] #[ApiProperty(description: 'The organizational unit of the client')] @@ -52,6 +49,12 @@ final class ClientInput #[Groups(['client:write'])] public ?HardwareProfileOutput $hardwareProfile = null; + #[Groups(['client:write'])] + #[ApiProperty( + description: 'descriptions.client.validation' + )] + public ?array $position = ['x' => 0, 'y' => 0]; + public function __construct(?Client $client = null) { if (!$client) { @@ -65,7 +68,7 @@ final class ClientInput $this->netDriver = $client->getNetDriver(); $this->mac = $client->getMac(); $this->ip = $client->getIp(); - $this->status = $client->getStatus(); + $this->position = $client->getPosition(); if ($client->getMenu()) { $this->menu = new MenuOutput($client->getMenu()); @@ -89,9 +92,9 @@ final class ClientInput $client->setNetDriver($this->netDriver); $client->setMac($this->mac); $client->setIp($this->ip); - $client->setStatus($this->status); $client->setMenu($this->menu?->getEntity()); $client->setHardwareProfile($this->hardwareProfile?->getEntity()); + $client->setPosition($this->position); return $client; } diff --git a/src/Dto/Output/ClientOutput.php b/src/Dto/Output/ClientOutput.php index a7b49e3..97c0b5a 100644 --- a/src/Dto/Output/ClientOutput.php +++ b/src/Dto/Output/ClientOutput.php @@ -48,6 +48,9 @@ final class ClientOutput extends AbstractOutput #[ApiProperty(readableLink: true )] public ?HardwareProfileOutput $hardwareProfile = null; + #[Groups(['client:read'])] + public ?array $position = ['x' => 0, 'y' => 0]; + #[Groups(['client:read'])] public \DateTime $createdAt; @@ -74,8 +77,8 @@ final class ClientOutput extends AbstractOutput )->toArray(); $this->menu = $client->getMenu() ? new MenuOutput($client->getMenu()) : null; + $this->position = $client->getPosition(); $this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null; - $this->createdAt = $client->getCreatedAt(); $this->createdBy = $client->getCreatedBy(); } diff --git a/src/Entity/Client.php b/src/Entity/Client.php index 867fa3c..638ee48 100644 --- a/src/Entity/Client.php +++ b/src/Entity/Client.php @@ -54,6 +54,9 @@ class Client extends AbstractEntity #[ORM\Column(nullable: true)] private ?bool $validation = null; + #[ORM\Column(nullable: true)] + private ?array $position = ['x' => 0, 'y' => 0]; + public function __construct() { parent::__construct(); @@ -209,4 +212,16 @@ class Client extends AbstractEntity return $this; } + + public function getPosition(): ?array + { + return $this->position; + } + + public function setPosition(?array $position): static + { + $this->position = $position; + + return $this; + } } From b6ade24314ec6b7595c3c8a452f35d963c8b92d2 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 18 Jul 2024 12:43:57 +0200 Subject: [PATCH 3/9] refs #518. New endpoint 'search' --- config/packages/api_platform.yaml | 4 +- src/Controller/Api/SearchController.php | 37 ++++++++++++++++ src/Dto/Input/ClientInput.php | 30 ++++++------- src/Dto/Input/HardwareInput.php | 4 +- src/Dto/Input/HardwareTypeInput.php | 2 +- src/Dto/Input/ImageInput.php | 2 +- src/Dto/Input/NetworkSettingsInput.php | 10 ++--- src/Dto/Input/OperativeSystemInput.php | 2 +- src/Dto/Input/OrganizationalUnitInput.php | 2 +- src/OpenApi/OpenApiFactory.php | 51 +++++++++++++++++++++++ translations/validators.en.yaml | 26 ++++++++++++ translations/validators.es.yaml | 26 ++++++++++++ 12 files changed, 168 insertions(+), 28 deletions(-) create mode 100644 src/Controller/Api/SearchController.php diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 8871ca7..352abc2 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -1,6 +1,6 @@ api_platform: - title: 'OgCore API' - description: 'API Documentation for OgCore' + title: 'OgCore Api' + description: 'Api Documentation for OgCore' version: 1.0.0 path_segment_name_generator: 'api_platform.path_segment_name_generator.dash' formats: diff --git a/src/Controller/Api/SearchController.php b/src/Controller/Api/SearchController.php new file mode 100644 index 0000000..45a5d44 --- /dev/null +++ b/src/Controller/Api/SearchController.php @@ -0,0 +1,37 @@ +query->get('filters'); + if ($filters) { + $filters = json_decode($filters, true); + } + + $repository = $entityManager->getRepository(Client::class); + $queryBuilder = $repository->createQueryBuilder('e'); + + if (!empty($filters)) { + foreach ($filters as $field => $value) { + $queryBuilder->andWhere("e.$field = :$field") + ->setParameter($field, $value); + } + } + + $results = $queryBuilder->getQuery()->getResult(); + + return new JsonResponse($results); + } +} diff --git a/src/Dto/Input/ClientInput.php b/src/Dto/Input/ClientInput.php index 5f45ffc..564f65b 100644 --- a/src/Dto/Input/ClientInput.php +++ b/src/Dto/Input/ClientInput.php @@ -15,63 +15,63 @@ final class ClientInput #[Assert\NotBlank(message: 'validators.client.name.not_blank')] #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.name', - example: "examples.client.name", + description: 'El nombre del cliente', + example: 'Cliente 1', )] public ?string $name = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.serial_number', - example: "examples.client.serial_number" + description: 'La descripción del cliente', + example: 'Cliente descripcion 1', )] public ?string $serialNumber = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.network_interface', - example: "examples.client.network_interface" + description: 'La interfaz de red del cliente', + example: 'eth0' )] public ?string $netiface = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.network_driver', - example: "examples.client.network_driver" + description: 'El driver de red del cliente', + example: 'e1000e' )] public ?string $netDriver = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.mac_address', - example: "examples.client.mac_address" + description: 'La dirección MAC del cliente', + example: '00:00:00:00:00:00' )] public ?string $mac = null; #[Groups(['client:write'])] #[Assert\Ip(message: 'validators.ip_address.invalid')] #[ApiProperty( - description: 'descriptions.client.ip_address', - example: "examples.client.ip_address" + description: 'La dirección IP del cliente', + example: '192.168.1.1' )] public ?string $ip = null; #[Assert\NotNull(message: 'validators.organizational_unit.not_null')] #[Groups(['client:write', 'client:patch'])] #[ApiProperty( - description: 'descriptions.client.organizational_unit' + description: 'La unidad organizativa del cliente' )] public ?OrganizationalUnitOutput $organizationalUnit = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.menu' + description: 'El menú del cliente' )] public ?MenuOutput $menu = null; #[Groups(['client:write'])] #[ApiProperty( - description: 'descriptions.client.hardware_profile' + description: 'El perfil de hardware del cliente' )] public ?HardwareProfileOutput $hardwareProfile = null; diff --git a/src/Dto/Input/HardwareInput.php b/src/Dto/Input/HardwareInput.php index 7674310..4e763bd 100644 --- a/src/Dto/Input/HardwareInput.php +++ b/src/Dto/Input/HardwareInput.php @@ -10,7 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert; final class HardwareInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.hardware.name.not_blank')] #[Groups(['hardware:write'])] #[ApiProperty(description: 'The name of the hardware', example: "Hardware 1")] public ?string $name = null; @@ -19,7 +19,7 @@ final class HardwareInput #[ApiProperty(description: 'The description of the hardware', example: "Hardware 1 description")] public ?string $description = null; - #[Assert\NotNull] + #[Assert\NotNull(message: 'validators.hardware.type.not_null')] #[Groups(['hardware:write'])] #[ApiProperty(description: 'The type of the hardware', example: "Server")] public ?HardwareTypeOutput $type = null; diff --git a/src/Dto/Input/HardwareTypeInput.php b/src/Dto/Input/HardwareTypeInput.php index 9eb0ae1..7078492 100644 --- a/src/Dto/Input/HardwareTypeInput.php +++ b/src/Dto/Input/HardwareTypeInput.php @@ -8,7 +8,7 @@ use Symfony\Component\Validator\Constraints as Assert; class HardwareTypeInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.hardware_type.name.not_blank')] #[Groups(['hardware-type:write'])] public ?string $name = null; diff --git a/src/Dto/Input/ImageInput.php b/src/Dto/Input/ImageInput.php index d6623ab..10c46a5 100644 --- a/src/Dto/Input/ImageInput.php +++ b/src/Dto/Input/ImageInput.php @@ -11,7 +11,7 @@ use Symfony\Component\Validator\Constraints as Assert; final class ImageInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.image.name.not_blank')] #[Groups(['image:write'])] #[ApiProperty(description: 'The name of the image', example: "Image 1")] public ?string $name = null; diff --git a/src/Dto/Input/NetworkSettingsInput.php b/src/Dto/Input/NetworkSettingsInput.php index 750f0de..e32c732 100644 --- a/src/Dto/Input/NetworkSettingsInput.php +++ b/src/Dto/Input/NetworkSettingsInput.php @@ -18,11 +18,11 @@ class NetworkSettingsInput #[Groups(['organizational-unit:write'])] public ?string $proxy = null; - #[Assert\Ip()] + #[Assert\Ip(message: 'validators.network_settings.ip_address.invalid')] #[Groups(['organizational-unit:write'])] public ?string $dns = null; - #[Assert\Ip()] + #[Assert\Ip(message: 'validators.network_settings.ip_address.invalid')] #[Groups(['organizational-unit:write'])] public ?string $netmask = null; @@ -36,15 +36,15 @@ class NetworkSettingsInput #[Groups(['organizational-unit:write'])] public ?string $p2pMode = null; - #[Assert\GreaterThan(0)] + #[Assert\GreaterThan(0, message: 'validators.network_settings.p2p_time.invalid')] #[Groups(['organizational-unit:write'])] public ?int $p2pTime = null; - #[Assert\Ip()] + #[Assert\Ip(message: 'validators.network_settings.ip_address.invalid')] #[Groups(['organizational-unit:write'])] public ?string $mcastIp = null; - #[Assert\GreaterThan(0)] + #[Assert\GreaterThan(0, message: 'validators.network_settings.mcast_speed.invalid')] #[Groups(['organizational-write:write'])] public ?int $mcastSpeed = null; diff --git a/src/Dto/Input/OperativeSystemInput.php b/src/Dto/Input/OperativeSystemInput.php index a38d809..8ebac93 100644 --- a/src/Dto/Input/OperativeSystemInput.php +++ b/src/Dto/Input/OperativeSystemInput.php @@ -8,7 +8,7 @@ use Symfony\Component\Validator\Constraints as Assert; class OperativeSystemInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.operative_system.name.not_blank')] #[Groups(['operative-system:write'])] public ?string $name = null; diff --git a/src/Dto/Input/OrganizationalUnitInput.php b/src/Dto/Input/OrganizationalUnitInput.php index 7ce2c2b..1b83437 100644 --- a/src/Dto/Input/OrganizationalUnitInput.php +++ b/src/Dto/Input/OrganizationalUnitInput.php @@ -16,7 +16,7 @@ use Symfony\Component\Validator\Constraints as Assert; #[OrganizationalUnitParent] class OrganizationalUnitInput { - #[Assert\NotBlank] + #[Assert\NotBlank(message: 'validators.organizational_unit.name.not_blank')] #[Groups(['organizational-unit:write'])] public ?string $name = null; diff --git a/src/OpenApi/OpenApiFactory.php b/src/OpenApi/OpenApiFactory.php index d7a732e..9e0ae6f 100644 --- a/src/OpenApi/OpenApiFactory.php +++ b/src/OpenApi/OpenApiFactory.php @@ -19,6 +19,8 @@ final readonly class OpenApiFactory implements OpenApiFactoryInterface $openApi = $this->decorated->__invoke($context); $this->addRefreshToken($openApi); + $this->addSearchEndpoint($openApi); + return $openApi; } @@ -75,4 +77,53 @@ final readonly class OpenApiFactory implements OpenApiFactoryInterface ) )); } + private function addSearchEndpoint(OpenApi $openApi): void + { + $openApi + ->getPaths() + ->addPath('/search', (new Model\PathItem())->withGet( + (new Model\Operation('getSearch')) + ->withTags(['Search']) + ->withResponses([ + Response::HTTP_OK => [ + 'description' => 'Search results', + 'content' => [ + 'application/json' => [ + 'schema' => [ + 'type' => 'object', + 'properties' => [ + 'results' => [ + 'type' => 'array', + 'items' => [ + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'type' => 'integer', + 'example' => 1, + ], + 'name' => [ + 'type' => 'string', + 'example' => 'Item name', + ], + ], + ], + ], + ], + ], + ], + ], + ], + ]) + ->withSummary('Search for items') + ->withParameters([ + new Model\Parameter( + 'query', + 'query', + 'Search query parameter', + true, + false, + ), + ]) + )); + } } \ No newline at end of file diff --git a/translations/validators.en.yaml b/translations/validators.en.yaml index e205608..e5b6e69 100644 --- a/translations/validators.en.yaml +++ b/translations/validators.en.yaml @@ -9,5 +9,31 @@ validators: not_null: 'The organizational unit should not be null.' view: + name: + not_blank: 'The name should not be blank.' + + hardware: + name: + not_blank: 'The name should not be blank.' + type: + not_null: 'The type should not be null.' + + hardware_type: + name: + not_blank: 'The name should not be blank.' + + image: + name: + not_blank: 'The name should not be blank.' + + network_settings: + ip_address: + invalid: 'The IP address "{{ value }}" is not valid.' + p2p_time: + invalid: 'The P2P time "{{ value }}" is not valid.' + mcast_speed: + invalid: 'The MCAST speed "{{ value }}" is not valid.' + + operative_system: name: not_blank: 'The name should not be blank.' \ No newline at end of file diff --git a/translations/validators.es.yaml b/translations/validators.es.yaml index 732c4f0..9bf6d15 100644 --- a/translations/validators.es.yaml +++ b/translations/validators.es.yaml @@ -9,5 +9,31 @@ validators: not_null: 'La unidad organizativa no debería estar vacía.' view: + name: + not_blank: 'El nombre no debería estar vacío.' + + hardware: + name: + not_blank: 'El nombre no debería estar vacío.' + type: + not_null: 'El tipo no debería estar vacío.' + + hardware_type: + name: + not_blank: 'El nombre no debería estar vacío.' + + image: + name: + not_blank: 'El nombre no debería estar vacío.' + + network_settings: + ip_address: + invalid: 'La dirección IP "{{ value }}" no es válida.' + p2p_time: + invalid: 'El tiempo P2P "{{ value }}" no es válido.' + mcast_speed: + invalid: 'La velocidad MCAST "{{ value }}" no es válida.' + + operative_system: name: not_blank: 'El nombre no debería estar vacío.' \ No newline at end of file From 56003409e69f04bf9ffa1152deca9ba2682b6e06 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 29 Jul 2024 09:07:11 +0200 Subject: [PATCH 4/9] Change migration name --- src/Command/Migration/MigrateClientsCommand.php | 1 + .../Migration/MigrateHardwareAndHardwareProfileCommand.php | 2 +- .../Migration/MigrateSoftwareAndSoftwareProfileCommand.php | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Command/Migration/MigrateClientsCommand.php b/src/Command/Migration/MigrateClientsCommand.php index 1dc517c..b5117a4 100644 --- a/src/Command/Migration/MigrateClientsCommand.php +++ b/src/Command/Migration/MigrateClientsCommand.php @@ -62,6 +62,7 @@ class MigrateClientsCommand extends Command $clientEntity->setNetdriver($client['netdriver']); $clientEntity->setMac($client['mac']); $clientEntity->setIp($client['ip']); + $clientEntity->setPosition(['x' => 0, 'y' => 0]); } $migrationId = $client['ordenadores.grupoid'] === 0 ? $client['ordenadores.idaula'] : $client['ordenadores.grupoid']; diff --git a/src/Command/Migration/MigrateHardwareAndHardwareProfileCommand.php b/src/Command/Migration/MigrateHardwareAndHardwareProfileCommand.php index 0fdfd04..e71b417 100644 --- a/src/Command/Migration/MigrateHardwareAndHardwareProfileCommand.php +++ b/src/Command/Migration/MigrateHardwareAndHardwareProfileCommand.php @@ -14,7 +14,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'opengnsys:migrate-hardware-profile', description: 'Migrate hardware and hardware profile data')] +#[AsCommand(name: 'opengnsys:migration:hardware-profile', description: 'Migrate hardware and hardware profile data')] class MigrateHardwareAndHardwareProfileCommand extends Command { public function __construct( diff --git a/src/Command/Migration/MigrateSoftwareAndSoftwareProfileCommand.php b/src/Command/Migration/MigrateSoftwareAndSoftwareProfileCommand.php index 6daf869..667de64 100644 --- a/src/Command/Migration/MigrateSoftwareAndSoftwareProfileCommand.php +++ b/src/Command/Migration/MigrateSoftwareAndSoftwareProfileCommand.php @@ -13,7 +13,7 @@ use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -#[AsCommand(name: 'opengnsys:migrate-software-profile', description: 'Migrate software and software profile data')] +#[AsCommand(name: 'opengnsys:migration:software-profile', description: 'Migrate software and software profile data')] class MigrateSoftwareAndSoftwareProfileCommand extends Command { public function __construct( From d44882c59c235e6b36e0c961918af0ec8fc763b5 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 1 Aug 2024 15:21:14 +0200 Subject: [PATCH 5/9] refs #512. Added new field 'user' --- migrations/Version20240801130155.php | 35 ++++++++++++++++++++ src/Doctrine/UserViewExtension.php | 46 +++++++++++++++++++++++++++ src/Entity/View.php | 15 +++++++++ src/State/Processor/ViewProcessor.php | 5 ++- 4 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 migrations/Version20240801130155.php create mode 100644 src/Doctrine/UserViewExtension.php diff --git a/migrations/Version20240801130155.php b/migrations/Version20240801130155.php new file mode 100644 index 0000000..5c389bf --- /dev/null +++ b/migrations/Version20240801130155.php @@ -0,0 +1,35 @@ +addSql('ALTER TABLE view ADD user_id INT DEFAULT NULL'); + $this->addSql('ALTER TABLE view ADD CONSTRAINT FK_FEFDAB8EA76ED395 FOREIGN KEY (user_id) REFERENCES user (id)'); + $this->addSql('CREATE INDEX IDX_FEFDAB8EA76ED395 ON view (user_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE view DROP FOREIGN KEY FK_FEFDAB8EA76ED395'); + $this->addSql('DROP INDEX IDX_FEFDAB8EA76ED395 ON view'); + $this->addSql('ALTER TABLE view DROP user_id'); + } +} diff --git a/src/Doctrine/UserViewExtension.php b/src/Doctrine/UserViewExtension.php new file mode 100644 index 0000000..f5f8938 --- /dev/null +++ b/src/Doctrine/UserViewExtension.php @@ -0,0 +1,46 @@ +addWhere($queryBuilder, $resourceClass); + + } + + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, ?Operation $operation = null, array $context = []): void + { + $this->addWhere($queryBuilder, $resourceClass); + } + + private function addWhere(QueryBuilder $queryBuilder, string $resourceClass): void + { + if (View::class !== $resourceClass ) { + return; + } + + /** @var User $user */ + $user = $this->security->getUser(); + + $rootAlias = $queryBuilder->getRootAliases()[0]; + $queryBuilder->andWhere(sprintf('%s.user = :current_user', $rootAlias)); + $queryBuilder->setParameter('current_user', $user->getId()); + } +} \ No newline at end of file diff --git a/src/Entity/View.php b/src/Entity/View.php index 4df348a..db4608f 100644 --- a/src/Entity/View.php +++ b/src/Entity/View.php @@ -17,6 +17,9 @@ class View extends \App\Entity\AbstractEntity #[ORM\Column(type: Types::JSON, nullable: true)] private ?array $filters = []; + #[ORM\ManyToOne] + private ?User $user = null; + public function setName(string $name): static { $this->name = $name; @@ -47,4 +50,16 @@ class View extends \App\Entity\AbstractEntity return $this; } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): static + { + $this->user = $user; + + return $this; + } } diff --git a/src/State/Processor/ViewProcessor.php b/src/State/Processor/ViewProcessor.php index a9914d5..3e03780 100644 --- a/src/State/Processor/ViewProcessor.php +++ b/src/State/Processor/ViewProcessor.php @@ -12,12 +12,14 @@ use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\ViewInput; use App\Dto\Output\ViewOutput; use App\Repository\ViewRepository; +use Symfony\Component\Security\Core\Security; readonly class ViewProcessor implements ProcessorInterface { public function __construct( private ViewRepository $viewRepository, - private ValidatorInterface $validator + private ValidatorInterface $validator, + private Security $security ) { } @@ -52,6 +54,7 @@ readonly class ViewProcessor implements ProcessorInterface } $view = $data->createOrUpdateEntity($entity); + $view->setUser($this->security->getUser()); $this->validator->validate($view); $this->viewRepository->save($view); From 5bb75fd6a97ac6d3d624a6b0a88f439cd2a7847a Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 1 Aug 2024 15:51:43 +0200 Subject: [PATCH 6/9] refs #512. Added new field 'user' --- tests/Functional/ViewTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Functional/ViewTest.php b/tests/Functional/ViewTest.php index 434ffa9..5a0b016 100644 --- a/tests/Functional/ViewTest.php +++ b/tests/Functional/ViewTest.php @@ -35,9 +35,9 @@ class ViewTest extends AbstractTest */ public function testGetCollectionViews(): void { - UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + $user = UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); - ViewFactory::createMany(10); + ViewFactory::createMany(10, ['user' => $user]); $this->createClientWithCredentials()->request('GET', '/views'); $this->assertResponseStatusCodeSame(Response::HTTP_OK); @@ -59,9 +59,9 @@ class ViewTest extends AbstractTest */ public function testCreateView(): void { - UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + $user = UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); - ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true]); + ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true, 'user' => $user]); $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_CREATE]); $this->createClientWithCredentials()->request('POST', '/views',['json' => [ @@ -87,9 +87,9 @@ class ViewTest extends AbstractTest */ public function testUpdateView(): void { - UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + $user = UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); - ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true]); + ViewFactory::createOne(['name' => self::VIEW_CREATE, 'favourite' => true, 'user' => $user]); $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_CREATE]); $this->createClientWithCredentials()->request('PUT', $viewIri, ['json' => [ @@ -114,9 +114,9 @@ class ViewTest extends AbstractTest */ public function testDeleteView(): void { - UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + $user = UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); - ViewFactory::createOne(['name' => self::VIEW_DELETE, 'favourite' => true]); + ViewFactory::createOne(['name' => self::VIEW_DELETE, 'favourite' => true, 'user' => $user]); $viewIri = $this->findIriBy(View::class, ['name' => self::VIEW_DELETE]); $this->createClientWithCredentials()->request('DELETE', $viewIri); From a1822919366723f68ca35bc9091fb761db190892 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 5 Aug 2024 10:33:30 +0200 Subject: [PATCH 7/9] Added partition migration --- .../MigratePartitionClientCommand.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/Command/Migration/MigratePartitionClientCommand.php diff --git a/src/Command/Migration/MigratePartitionClientCommand.php b/src/Command/Migration/MigratePartitionClientCommand.php new file mode 100644 index 0000000..32dd3ca --- /dev/null +++ b/src/Command/Migration/MigratePartitionClientCommand.php @@ -0,0 +1,81 @@ +doctrine->getManager('og_1'); + + $clientRepository = $this->entityManager->getRepository(Client::class); + $operativeSystemRepository = $this->entityManager->getRepository(OperativeSystem::class); + + /** Obtener las particiones de los clientes de la base de datos antigua **/ + $rsmPartitions = new ResultSetMapping(); + $rsmPartitions->addScalarResult('idordenador', 'idordenador'); + $rsmPartitions->addScalarResult('idnombreso', 'idnombreso'); + $rsmPartitions->addScalarResult('numdisk', 'numdisk'); + $rsmPartitions->addScalarResult('numpar', 'numpar'); + $rsmPartitions->addScalarResult('codpar', 'codpar'); + $rsmPartitions->addScalarResult('tamano', 'tamano'); + $rsmPartitions->addScalarResult('uso', 'uso'); + + $partitionQuery = $oldDatabaseEntityManager->createNativeQuery('SELECT idordenador, idnombreso, numdisk, numpar, codpar, tamano, uso FROM ordenadores_particiones', $rsmPartitions); + $partitions = $partitionQuery->getResult(); + + /** Particiones **/ + $output->writeln("PARTICIONES TOTAL: ". count($partitions)); + foreach ($partitions as $partition){ + $clientEntity = $clientRepository->findOneBy(['migrationId' => $partition['idordenador']]); + if(!$clientEntity){ + $output->writeln("No se ha encontrado el cliente con id: ". $partition['idordenador']); + continue; + } + + $operativeSystemEntity = $operativeSystemRepository->findOneBy(['migrationId' => $partition['idnombreso']]); + if(!$operativeSystemEntity){ + $output->writeln("No se ha encontrado el sistema operativo con id: ". $partition['idnombreso']); + continue; + } + + $partitionEntity = new Partition(); + $partitionEntity->setDiskNumber($partition['numdisk']); + $partitionEntity->setPartitionNumber($partition['numpar']); + $partitionEntity->setPartitionCode($partition['codpar']); + $partitionEntity->setSize($partition['tamano']); + $partitionEntity->setMemoryUsage($partition['uso']); + $partitionEntity->setClient($clientEntity); + $partitionEntity->setOperativeSystem($operativeSystemEntity); + + $this->entityManager->persist($partitionEntity); + $this->entityManager->flush(); + } + return 1; + } +} \ No newline at end of file From a256e6c92cb3c720d11544bac1d44aee117e1e96 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 5 Aug 2024 11:13:25 +0200 Subject: [PATCH 8/9] Added clientOutput partition field --- src/Dto/Output/ClientOutput.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Dto/Output/ClientOutput.php b/src/Dto/Output/ClientOutput.php index a7b49e3..97c0b5a 100644 --- a/src/Dto/Output/ClientOutput.php +++ b/src/Dto/Output/ClientOutput.php @@ -48,6 +48,9 @@ final class ClientOutput extends AbstractOutput #[ApiProperty(readableLink: true )] public ?HardwareProfileOutput $hardwareProfile = null; + #[Groups(['client:read'])] + public ?array $position = ['x' => 0, 'y' => 0]; + #[Groups(['client:read'])] public \DateTime $createdAt; @@ -74,8 +77,8 @@ final class ClientOutput extends AbstractOutput )->toArray(); $this->menu = $client->getMenu() ? new MenuOutput($client->getMenu()) : null; + $this->position = $client->getPosition(); $this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null; - $this->createdAt = $client->getCreatedAt(); $this->createdBy = $client->getCreatedBy(); } From e11556174b309901d5d9a6f2ce938b5106a82249 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 5 Aug 2024 11:23:57 +0200 Subject: [PATCH 9/9] Added clientOutput partition field --- src/Dto/Output/ClientOutput.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Dto/Output/ClientOutput.php b/src/Dto/Output/ClientOutput.php index 97c0b5a..a7b49e3 100644 --- a/src/Dto/Output/ClientOutput.php +++ b/src/Dto/Output/ClientOutput.php @@ -48,9 +48,6 @@ final class ClientOutput extends AbstractOutput #[ApiProperty(readableLink: true )] public ?HardwareProfileOutput $hardwareProfile = null; - #[Groups(['client:read'])] - public ?array $position = ['x' => 0, 'y' => 0]; - #[Groups(['client:read'])] public \DateTime $createdAt; @@ -77,8 +74,8 @@ final class ClientOutput extends AbstractOutput )->toArray(); $this->menu = $client->getMenu() ? new MenuOutput($client->getMenu()) : null; - $this->position = $client->getPosition(); $this->hardwareProfile = $client->getHardwareProfile() ? new HardwareProfileOutput($client->getHardwareProfile()) : null; + $this->createdAt = $client->getCreatedAt(); $this->createdBy = $client->getCreatedBy(); }