From cf0eb36aff6432005bf4e44593067329e1bb5929 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 23 May 2024 15:53:40 +0200 Subject: [PATCH 01/15] refs #375. Crear modelo de base de datos de Usuario --- README.md | 6 +- composer.json | 2 + composer.lock | 748 +++++++++++++++++- config/bundles.php | 1 + config/packages/api_platform.yaml | 7 + config/packages/ramsey_uuid_doctrine.yaml | 4 + config/packages/stof_doctrine_extensions.yaml | 4 + config/services/api_platform.yaml | 28 + docker/Dockerfile-php | 5 - src/Entity/AbstractEntity.php | 50 ++ src/Entity/User.php | 47 +- symfony.lock | 33 + 12 files changed, 918 insertions(+), 17 deletions(-) create mode 100644 config/packages/ramsey_uuid_doctrine.yaml create mode 100644 config/packages/stof_doctrine_extensions.yaml create mode 100644 config/services/api_platform.yaml create mode 100644 src/Entity/AbstractEntity.php diff --git a/README.md b/README.md index df93e13..7f9feaf 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Y deberiamos ver algo parecido a : ```sh docker exec ogcore-php composer install -docker exec ogcore-php symfony console lexik:jwt:generate-keypair --overwrite +docker exec ogcore-php php bin/console lexik:jwt:generate-keypair --overwrite ``` Comprobamos, que el contenedor de Nginx, tiene el puerto 8080 levantado correctamente, asi que tan solo tendremos que @@ -55,9 +55,9 @@ Para poder actualizar la base de datos: Para inicializar la base de datos: ```sh -docker exec ogcore-php symfony console doctrine:migrations:migrate --no-interaction +docker exec ogcore-php php bin/consoledoctrine:migrations:migrate --no-interaction ``` ```sh -docker exec ogcore-php symfony console doctrine:fixtures:load --no-interaction +docker exec ogcore-php php bin/console doctrine:fixtures:load --no-interaction ``` diff --git a/composer.json b/composer.json index d3ac35b..a96e951 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,8 @@ "nelmio/cors-bundle": "^2.4", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", + "ramsey/uuid-doctrine": "^2.0", + "stof/doctrine-extensions-bundle": "^1.10", "symfony/asset": "6.4.*", "symfony/console": "6.4.*", "symfony/dotenv": "6.4.*", diff --git a/composer.lock b/composer.lock index d56a6e3..e4e592c 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": "d621ba18aa396cb809e0ea1ea8b815d5", + "content-hash": "8a178ee08c8bef147097b85e84af9dd7", "packages": [ { "name": "api-platform/core", @@ -194,6 +194,191 @@ }, "time": "2024-05-10T11:16:59+00:00" }, + { + "name": "behat/transliterator", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Behat/Transliterator.git", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Behat/Transliterator/zipball/baac5873bac3749887d28ab68e2f74db3a4408af", + "reference": "baac5873bac3749887d28ab68e2f74db3a4408af", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "require-dev": { + "chuyskywalker/rolling-curl": "^3.1", + "php-yaoi/php-yaoi": "^1.0", + "phpunit/phpunit": "^8.5.25 || ^9.5.19" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Behat\\Transliterator\\": "src/Behat/Transliterator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Artistic-1.0" + ], + "description": "String transliterator", + "keywords": [ + "i18n", + "slug", + "transliterator" + ], + "support": { + "issues": "https://github.com/Behat/Transliterator/issues", + "source": "https://github.com/Behat/Transliterator/tree/v1.5.0" + }, + "time": "2022-03-30T09:27:43+00:00" + }, + { + "name": "brick/math", + "version": "0.12.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1", + "reference": "f510c0a40911935b77b86859eb5223d58d660df1", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "5.16.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.12.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2023-11-29T23:19:16+00:00" + }, + { + "name": "doctrine/annotations", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^2 || ^3", + "ext-tokenizer": "*", + "php": "^7.2 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", + "vimeo/psalm": "^4.10" + }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/2.0.1" + }, + "time": "2023-02-02T22:02:53+00:00" + }, { "name": "doctrine/cache", "version": "2.2.0", @@ -373,6 +558,97 @@ ], "time": "2024-04-18T06:56:21+00:00" }, + { + "name": "doctrine/common", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", + "reference": "0aad4b7ab7ce8c6602dfbb1e1a24581275fb9d1a", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0 || ^3.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9.0 || ^10.0", + "doctrine/collections": "^1", + "phpstan/phpstan": "^1.4.1", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^6.1", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.4.4" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2024-04-16T13:35:33+00:00" + }, { "name": "doctrine/dbal", "version": "3.8.4", @@ -1417,6 +1693,133 @@ }, "time": "2024-05-08T08:12:09+00:00" }, + { + "name": "gedmo/doctrine-extensions", + "version": "v3.14.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine-extensions/DoctrineExtensions.git", + "reference": "3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf", + "reference": "3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf", + "shasum": "" + }, + "require": { + "behat/transliterator": "^1.2", + "doctrine/annotations": "^1.13 || ^2.0", + "doctrine/collections": "^1.2 || ^2.0", + "doctrine/common": "^2.13 || ^3.0", + "doctrine/event-manager": "^1.2 || ^2.0", + "doctrine/persistence": "^2.2 || ^3.0", + "php": "^7.4 || ^8.0", + "psr/cache": "^1 || ^2 || ^3", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0" + }, + "conflict": { + "doctrine/dbal": "<3.2", + "doctrine/mongodb-odm": "<2.3", + "doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1", + "sebastian/comparator": "<2.0" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/dbal": "^3.2", + "doctrine/doctrine-bundle": "^2.3", + "doctrine/mongodb-odm": "^2.3", + "doctrine/orm": "^2.14.0", + "friendsofphp/php-cs-fixer": "^3.14.0", + "nesbot/carbon": "^2.71 || 3.x-dev as 3.0", + "phpstan/phpstan": "^1.10.2", + "phpstan/phpstan-doctrine": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.6", + "rector/rector": "^0.18", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/mongodb-odm": "to use the extensions with the MongoDB ODM", + "doctrine/orm": "to use the extensions with the ORM" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.13-dev" + } + }, + "autoload": { + "psr-4": { + "Gedmo\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gediminas Morkevicius", + "email": "gediminas.morkevicius@gmail.com" + }, + { + "name": "Gustavo Falco", + "email": "comfortablynumb84@gmail.com" + }, + { + "name": "David Buchmann", + "email": "david@liip.ch" + } + ], + "description": "Doctrine behavioral extensions", + "homepage": "http://gediminasm.org/", + "keywords": [ + "Blameable", + "behaviors", + "doctrine", + "extensions", + "gedmo", + "loggable", + "nestedset", + "odm", + "orm", + "sluggable", + "sortable", + "timestampable", + "translatable", + "tree", + "uploadable" + ], + "support": { + "email": "gediminas.morkevicius@gmail.com", + "issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues", + "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.14.0", + "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" + }, + "funding": [ + { + "url": "https://github.com/l3pp4rd", + "type": "github" + }, + { + "url": "https://github.com/mbabker", + "type": "github" + }, + { + "url": "https://github.com/phansys", + "type": "github" + }, + { + "url": "https://github.com/stof", + "type": "github" + } + ], + "time": "2023-12-03T09:10:34+00:00" + }, { "name": "gesdinet/jwt-refresh-token-bundle", "version": "v1.3.0", @@ -2339,6 +2742,349 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "ramsey/collection", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.28.3", + "fakerphp/faker": "^1.21", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^1.0", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-rc1", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/collection", + "type": "tidelift" + } + ], + "time": "2022-12-31T21:50:55+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.7.6", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088", + "reference": "91039bc1faa45ba123c4328958e620d382ec7088", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12", + "ext-json": "*", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.10", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.8", + "ergebnis/composer-normalize": "^2.15", + "mockery/mockery": "^1.3", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.2", + "php-mock/php-mock-mockery": "^1.3", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^8.5 || ^9", + "ramsey/composer-repl": "^1.4", + "slevomat/coding-standard": "^8.4", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.9" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.7.6" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid", + "type": "tidelift" + } + ], + "time": "2024-04-27T21:32:50+00:00" + }, + { + "name": "ramsey/uuid-doctrine", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid-doctrine.git", + "reference": "b002676be0e5e342d857c47f1b68e24de6841d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid-doctrine/zipball/b002676be0e5e342d857c47f1b68e24de6841d08", + "reference": "b002676be0e5e342d857c47f1b68e24de6841d08", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^2.8 || ^3.0", + "php": "^7.4 || ^8.0", + "ramsey/uuid": "^3.9.7 || ^4.0" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "doctrine/orm": "^2.5", + "ergebnis/composer-normalize": "^2.28.3", + "mockery/mockery": "^1.5", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpcsstandards/phpcsutils": "^1.0.0-alpha4", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^9.5", + "psalm/plugin-mockery": "^1.1", + "psalm/plugin-phpunit": "^0.18.4", + "ramsey/coding-standard": "^2.0.3", + "ramsey/conventional-commits": "^1.3", + "vimeo/psalm": "^5.4" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\Doctrine\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "Use ramsey/uuid as a Doctrine field type.", + "keywords": [ + "database", + "doctrine", + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid-doctrine/issues", + "source": "https://github.com/ramsey/uuid-doctrine/tree/2.0.0" + }, + "funding": [ + { + "url": "https://github.com/ramsey", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/ramsey/uuid-doctrine", + "type": "tidelift" + } + ], + "time": "2022-12-20T23:38:28+00:00" + }, + { + "name": "stof/doctrine-extensions-bundle", + "version": "v1.10.1", + "source": { + "type": "git", + "url": "https://github.com/stof/StofDoctrineExtensionsBundle.git", + "reference": "299d5333ce83941069852be36b949abbc776bf1d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/299d5333ce83941069852be36b949abbc776bf1d", + "reference": "299d5333ce83941069852be36b949abbc776bf1d", + "shasum": "" + }, + "require": { + "gedmo/doctrine-extensions": "^3.5.0", + "php": "^7.2.5 || ^8.0", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/mime": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1", + "symfony/security-core": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "to use the ORM extensions", + "doctrine/mongodb-odm-bundle": "to use the MongoDB ODM extensions", + "symfony/mime": "To use the Mime component integration for Uploadable" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Stof\\DoctrineExtensionsBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christophe Coevoet", + "email": "stof@notk.org" + } + ], + "description": "Integration of the gedmo/doctrine-extensions with Symfony", + "homepage": "https://github.com/stof/StofDoctrineExtensionsBundle", + "keywords": [ + "behaviors", + "doctrine2", + "extensions", + "gedmo", + "loggable", + "nestedset", + "sluggable", + "sortable", + "timestampable", + "translatable", + "tree" + ], + "support": { + "issues": "https://github.com/stof/StofDoctrineExtensionsBundle/issues", + "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.10.1" + }, + "time": "2023-12-09T09:33:39+00:00" + }, { "name": "symfony/asset", "version": "v6.4.7", diff --git a/config/bundles.php b/config/bundles.php index 3f4a386..4aa19fe 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -14,4 +14,5 @@ return [ Zenstruck\Foundry\ZenstruckFoundryBundle::class => ['dev' => true, 'test' => true], Gesdinet\JWTRefreshTokenBundle\GesdinetJWTRefreshTokenBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], ]; diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index fe1e993..00c8296 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -2,14 +2,21 @@ api_platform: title: 'OgCore API' description: 'API Documentation for OgCore' version: 1.0.0 + path_segment_name_generator: 'api_platform.path_segment_name_generator.dash' formats: jsonld: ['application/ld+json'] + mapping: + paths: ['%kernel.project_dir%/config/api_platform'] + defaults: + pagination_client_items_per_page: true docs_formats: jsonld: ['application/ld+json'] jsonopenapi: ['application/vnd.openapi+json'] html: ['text/html'] keep_legacy_inflector: false use_symfony_listeners: true + graphql: + enabled: true swagger: versions: [3] api_keys: diff --git a/config/packages/ramsey_uuid_doctrine.yaml b/config/packages/ramsey_uuid_doctrine.yaml new file mode 100644 index 0000000..cfc3036 --- /dev/null +++ b/config/packages/ramsey_uuid_doctrine.yaml @@ -0,0 +1,4 @@ +doctrine: + dbal: + types: + uuid: 'Ramsey\Uuid\Doctrine\UuidType' diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml new file mode 100644 index 0000000..b258add --- /dev/null +++ b/config/packages/stof_doctrine_extensions.yaml @@ -0,0 +1,4 @@ +# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html +# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc +stof_doctrine_extensions: + default_locale: en_US diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml new file mode 100644 index 0000000..f735df2 --- /dev/null +++ b/config/services/api_platform.yaml @@ -0,0 +1,28 @@ +services: + api_platform.filter.user.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id' : ~, 'username': ~ } + $orderParameterName: 'order' + tags: + - ['api_platform.filter' ] + + api_platform.filter.user.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'username': 'partial' }] + tags: + - ['api_platform.filter' ] + + api_platform.filter.user_group.order: + parent: 'api_platform.doctrine.orm.order_filter' + arguments: + $properties: { 'id': ~, 'name': ~ } + $orderParameterName: 'order' + tags: + - [ 'api_platform.filter' ] + + api_platform.filter.user_group.search: + parent: 'api_platform.doctrine.orm.search_filter' + arguments: [ { 'id': 'exact', 'name': 'partial' } ] + tags: + - [ 'api_platform.filter' ] \ No newline at end of file diff --git a/docker/Dockerfile-php b/docker/Dockerfile-php index 02a3f56..0ce2698 100644 --- a/docker/Dockerfile-php +++ b/docker/Dockerfile-php @@ -18,8 +18,3 @@ RUN apk add --no-cache bash git jq moreutils openssh rsync yq ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ RUN chmod +x /usr/local/bin/install-php-extensions RUN install-php-extensions sockets - -# Install Symfony binary -RUN apk add --no-cache bash \ - && curl -1sLf 'https://dl.cloudsmith.io/public/symfony/stable/setup.alpine.sh' | bash \ - && apk add symfony-cli diff --git a/src/Entity/AbstractEntity.php b/src/Entity/AbstractEntity.php new file mode 100644 index 0000000..a59a32d --- /dev/null +++ b/src/Entity/AbstractEntity.php @@ -0,0 +1,50 @@ +uuid = Uuid::uuid7(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getMigrationId(): ?string + { + return $this->migrationId; + } + + public function setMigrationId(?string $migrationId): self + { + $this->migrationId = $migrationId; + + return $this; + } +} \ No newline at end of file diff --git a/src/Entity/User.php b/src/Entity/User.php index 48bedd2..62daf7f 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -3,19 +3,16 @@ namespace App\Entity; use App\Repository\UserRepository; +use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] -class User implements UserInterface, PasswordAuthenticatedUserInterface +class User extends AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface { - #[ORM\Id] - #[ORM\GeneratedValue] - #[ORM\Column] - private ?int $id = null; - #[ORM\Column(length: 180)] private ?string $username = null; @@ -31,9 +28,16 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column] private ?string $password = null; - public function getId(): ?int + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: UserGroup::class, mappedBy: 'users')] + private Collection $userGroups; + + public function __construct() { - return $this->id; + parent::__construct(); + $this->userGroups = new ArrayCollection(); } public function getUsername(): ?string @@ -105,4 +109,31 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface // If you store any temporary, sensitive data on the user, clear it here // $this->plainPassword = null; } + + /** + * @return Collection + */ + public function getUserGroups(): Collection + { + return $this->userGroups; + } + + public function addUserGroup(UserGroup $userGroup): static + { + if (!$this->userGroups->contains($userGroup)) { + $this->userGroups->add($userGroup); + $userGroup->addUser($this); + } + + return $this; + } + + public function removeUserGroup(UserGroup $userGroup): static + { + if ($this->userGroups->removeElement($userGroup)) { + $userGroup->removeUser($this); + } + + return $this; + } } diff --git a/symfony.lock b/symfony.lock index 3e86e9a..956ed64 100644 --- a/symfony.lock +++ b/symfony.lock @@ -13,6 +13,15 @@ "src/ApiResource/.gitignore" ] }, + "doctrine/annotations": { + "version": "2.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.10", + "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" + } + }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { @@ -90,6 +99,30 @@ "config/packages/nelmio_cors.yaml" ] }, + "ramsey/uuid-doctrine": { + "version": "2.0", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.3", + "ref": "471aed0fbf5620b8d7f92b7a5ebbbf6c0945c27a" + }, + "files": [ + "config/packages/ramsey_uuid_doctrine.yaml" + ] + }, + "stof/doctrine-extensions-bundle": { + "version": "1.10", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "1.2", + "ref": "e805aba9eff5372e2d149a9ff56566769e22819d" + }, + "files": [ + "config/packages/stof_doctrine_extensions.yaml" + ] + }, "symfony/console": { "version": "6.4", "recipe": { From 94d3d3f541b855e30411ed562b3e0291a912af03 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 23 May 2024 15:55:53 +0200 Subject: [PATCH 02/15] refs #376. Crear modelo de base de datos de roles --- config/api_platform/User.yaml | 24 ++++++++ config/api_platform/UserGroup.yaml | 23 ++++++++ migrations/Version20240523091838.php | 33 +++++++++++ migrations/Version20240523093000.php | 31 ++++++++++ migrations/Version20240523093036.php | 31 ++++++++++ migrations/Version20240523094836.php | 37 ++++++++++++ src/Entity/UserGroup.php | 80 ++++++++++++++++++++++++++ src/Repository/UserGroupRepository.php | 43 ++++++++++++++ 8 files changed, 302 insertions(+) create mode 100644 config/api_platform/User.yaml create mode 100644 config/api_platform/UserGroup.yaml create mode 100644 migrations/Version20240523091838.php create mode 100644 migrations/Version20240523093000.php create mode 100644 migrations/Version20240523093036.php create mode 100644 migrations/Version20240523094836.php create mode 100644 src/Entity/UserGroup.php create mode 100644 src/Repository/UserGroupRepository.php diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml new file mode 100644 index 0000000..e1dd16e --- /dev/null +++ b/config/api_platform/User.yaml @@ -0,0 +1,24 @@ +resources: + App\Entity\User: + normalization_context: + groups: ['user:read'] + denormalization_context: + + groups: ['user:write'] + operations: + ApiPlatform\Metadata\GetCollection: + filters: + - 'api_platform.filter.user.order' + - 'api_platform.filter.user.search' + ApiPlatform\Metadata\Get: ~ + ApiPlatform\Metadata\Put: ~ + ApiPlatform\Metadata\Patch: ~ + ApiPlatform\Metadata\Post: ~ + ApiPlatform\Metadata\Delete: ~ + +properties: + App\Entity\User: + id: + identifier: false + uuid: + identifier: true \ No newline at end of file diff --git a/config/api_platform/UserGroup.yaml b/config/api_platform/UserGroup.yaml new file mode 100644 index 0000000..bb6931c --- /dev/null +++ b/config/api_platform/UserGroup.yaml @@ -0,0 +1,23 @@ +resources: + App\Entity\UserGroup: + normalization_context: + groups: ['user-group:read'] + denormalization_context: + groups: ['user-group:write'] + operations: + ApiPlatform\Metadata\GetCollection: + filters: + - 'api_platform.filter.user_group.order' + - 'api_platform.filter.user_group.search' + ApiPlatform\Metadata\Get: ~ + ApiPlatform\Metadata\Put: ~ + ApiPlatform\Metadata\Patch: ~ + ApiPlatform\Metadata\Post: ~ + ApiPlatform\Metadata\Delete: ~ + +properties: + App\Entity\UserGroup: + id: + identifier: false + uuid: + identifier: true \ No newline at end of file diff --git a/migrations/Version20240523091838.php b/migrations/Version20240523091838.php new file mode 100644 index 0000000..6f955c4 --- /dev/null +++ b/migrations/Version20240523091838.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE user ADD uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', ADD migration_id VARCHAR(255) DEFAULT NULL'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649D17F50A6 ON user (uuid)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_8D93D649D17F50A6 ON user'); + $this->addSql('ALTER TABLE user DROP uuid, DROP migration_id'); + } +} diff --git a/migrations/Version20240523093000.php b/migrations/Version20240523093000.php new file mode 100644 index 0000000..16f1851 --- /dev/null +++ b/migrations/Version20240523093000.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP created_at, DROP updated_at'); + } +} diff --git a/migrations/Version20240523093036.php b/migrations/Version20240523093036.php new file mode 100644 index 0000000..5d736be --- /dev/null +++ b/migrations/Version20240523093036.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user DROP created_at, DROP updated_at, DROP created_by, DROP updated_by'); + } +} diff --git a/migrations/Version20240523094836.php b/migrations/Version20240523094836.php new file mode 100644 index 0000000..478930d --- /dev/null +++ b/migrations/Version20240523094836.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE user_group (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, name VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', UNIQUE INDEX UNIQ_8F02BF9DD17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user_group_user (user_group_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_3AE4BD51ED93D47 (user_group_id), INDEX IDX_3AE4BD5A76ED395 (user_id), PRIMARY KEY(user_group_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD51ED93D47 FOREIGN KEY (user_group_id) REFERENCES user_group (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD5A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD51ED93D47'); + $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD5A76ED395'); + $this->addSql('DROP TABLE user_group'); + $this->addSql('DROP TABLE user_group_user'); + } +} diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php new file mode 100644 index 0000000..2962b4b --- /dev/null +++ b/src/Entity/UserGroup.php @@ -0,0 +1,80 @@ + + */ + #[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'userGroups')] + private Collection $users; + + public function __construct() + { + parent::__construct(); + + $this->users = new ArrayCollection(); + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + public function getRoles(): array + { + return $this->roles; + } + + public function setRoles(array $roles): static + { + $this->roles = $roles; + + return $this; + } + + /** + * @return Collection + */ + public function getUsers(): Collection + { + return $this->users; + } + + public function addUser(User $user): static + { + if (!$this->users->contains($user)) { + $this->users->add($user); + } + + return $this; + } + + public function removeUser(User $user): static + { + $this->users->removeElement($user); + + return $this; + } +} diff --git a/src/Repository/UserGroupRepository.php b/src/Repository/UserGroupRepository.php new file mode 100644 index 0000000..33ef735 --- /dev/null +++ b/src/Repository/UserGroupRepository.php @@ -0,0 +1,43 @@ + + */ +class UserGroupRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, UserGroup::class); + } + + // /** + // * @return UserGroup[] Returns an array of UserGroup objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('u.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?UserGroup + // { + // return $this->createQueryBuilder('u') + // ->andWhere('u.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} From 68ea45a1f996b5c00867927caa728065c235249e Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 24 May 2024 12:46:04 +0200 Subject: [PATCH 03/15] refs #375. Crear modelo de base de datos de usuario. Primera version API --- composer.json | 2 +- composer.lock | 225 +++++++----------- config/api_platform/User.yaml | 16 +- config/api_platform/UserGroup.yaml | 2 +- config/packages/api_platform.yaml | 8 +- config/packages/doctrine.yaml | 7 +- config/packages/stof_doctrine_extensions.yaml | 8 +- config/services.yaml | 5 + migrations/Version20240524063952.php | 33 +++ migrations/Version20240524065625.php | 38 +++ migrations/Version20240524083309.php | 35 +++ src/Dto/Input/UserGroupInput.php | 32 +++ src/Dto/Input/UserInput.php | 69 ++++++ src/Dto/Output/AbstractOutput.php | 25 ++ src/Dto/Output/UserOutput.php | 40 ++++ src/Entity/AbstractEntity.php | 5 + src/Entity/NameableTrait.php | 23 ++ src/Entity/NetworkSettings.php | 224 +++++++++++++++++ src/Entity/OrganizationalUnit.php | 169 +++++++++++++ src/Entity/ToggleableTrait.php | 21 ++ src/Entity/User.php | 61 ++++- src/Entity/UserGroup.php | 2 + src/Repository/.gitignore | 0 src/Repository/AbstractRepository.php | 35 +++ src/Repository/NetworkSettingsRepository.php | 43 ++++ .../OrganizationalUnitRepository.php | 43 ++++ src/Repository/UserRepository.php | 27 +-- src/State/Processor/UserProcessor.php | 62 +++++ src/State/Provider/UserProvider.php | 71 ++++++ symfony.lock | 9 - 30 files changed, 1140 insertions(+), 200 deletions(-) create mode 100644 migrations/Version20240524063952.php create mode 100644 migrations/Version20240524065625.php create mode 100644 migrations/Version20240524083309.php create mode 100644 src/Dto/Input/UserGroupInput.php create mode 100644 src/Dto/Input/UserInput.php create mode 100644 src/Dto/Output/AbstractOutput.php create mode 100644 src/Dto/Output/UserOutput.php create mode 100644 src/Entity/NameableTrait.php create mode 100644 src/Entity/NetworkSettings.php create mode 100644 src/Entity/OrganizationalUnit.php create mode 100644 src/Entity/ToggleableTrait.php delete mode 100644 src/Repository/.gitignore create mode 100644 src/Repository/AbstractRepository.php create mode 100644 src/Repository/NetworkSettingsRepository.php create mode 100644 src/Repository/OrganizationalUnitRepository.php create mode 100644 src/State/Processor/UserProcessor.php create mode 100644 src/State/Provider/UserProvider.php diff --git a/composer.json b/composer.json index a96e951..3312f07 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,7 @@ "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.12", "doctrine/doctrine-migrations-bundle": "^3.3", - "doctrine/orm": "^3.1", + "doctrine/orm": "^2.19.5", "gesdinet/jwt-refresh-token-bundle": "^1.3", "lexik/jwt-authentication-bundle": "^3.0", "nelmio/cors-bundle": "^2.4", diff --git a/composer.lock b/composer.lock index e4e592c..ef16494 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": "8a178ee08c8bef147097b85e84af9dd7", + "content-hash": "a03ea4d76774164d557cb2ecb327de33", "packages": [ { "name": "api-platform/core", @@ -303,82 +303,6 @@ ], "time": "2023-11-29T23:19:16+00:00" }, - { - "name": "doctrine/annotations", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", - "shasum": "" - }, - "require": { - "doctrine/lexer": "^2 || ^3", - "ext-tokenizer": "*", - "php": "^7.2 || ^8.0", - "psr/cache": "^1 || ^2 || ^3" - }, - "require-dev": { - "doctrine/cache": "^2.0", - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "symfony/cache": "^5.4 || ^6", - "vimeo/psalm": "^4.10" - }, - "suggest": { - "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "https://www.doctrine-project.org/projects/annotations.html", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "support": { - "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/2.0.1" - }, - "time": "2023-02-02T22:02:53+00:00" - }, { "name": "doctrine/cache", "version": "2.2.0", @@ -1023,16 +947,16 @@ }, { "name": "doctrine/event-manager", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/event-manager.git", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", "shasum": "" }, "require": { @@ -1042,10 +966,10 @@ "doctrine/common": "<2.9" }, "require-dev": { - "doctrine/coding-standard": "^10", + "doctrine/coding-standard": "^12", "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.28" + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" }, "type": "library", "autoload": { @@ -1094,7 +1018,7 @@ ], "support": { "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.0" + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" }, "funding": [ { @@ -1110,7 +1034,7 @@ "type": "tidelift" } ], - "time": "2022-10-12T20:59:15+00:00" + "time": "2024-05-22T20:47:39+00:00" }, { "name": "doctrine/inflector", @@ -1454,48 +1378,61 @@ }, { "name": "doctrine/orm", - "version": "3.1.3", + "version": "2.19.5", "source": { "type": "git", "url": "https://github.com/doctrine/orm.git", - "reference": "8ca99fdfdca3dc129ed93124e95e7f88b791a354" + "reference": "94986af28452da42a46a4489d1c958a2e5d710e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/orm/zipball/8ca99fdfdca3dc129ed93124e95e7f88b791a354", - "reference": "8ca99fdfdca3dc129ed93124e95e7f88b791a354", + "url": "https://api.github.com/repos/doctrine/orm/zipball/94986af28452da42a46a4489d1c958a2e5d710e5", + "reference": "94986af28452da42a46a4489d1c958a2e5d710e5", "shasum": "" }, "require": { "composer-runtime-api": "^2", - "doctrine/collections": "^2.2", - "doctrine/dbal": "^3.8.2 || ^4", + "doctrine/cache": "^1.12.1 || ^2.1.1", + "doctrine/collections": "^1.5 || ^2.1", + "doctrine/common": "^3.0.3", + "doctrine/dbal": "^2.13.1 || ^3.2", "doctrine/deprecations": "^0.5.3 || ^1", "doctrine/event-manager": "^1.2 || ^2", "doctrine/inflector": "^1.4 || ^2.0", "doctrine/instantiator": "^1.3 || ^2", - "doctrine/lexer": "^3", - "doctrine/persistence": "^3.3.1", + "doctrine/lexer": "^2 || ^3", + "doctrine/persistence": "^2.4 || ^3", "ext-ctype": "*", - "php": "^8.1", + "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3", - "symfony/console": "^5.4 || ^6.0 || ^7.0", - "symfony/var-exporter": "^6.3.9 || ^7.0" + "symfony/console": "^4.2 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php72": "^1.23", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "doctrine/annotations": "<1.13 || >= 3.0" }, "require-dev": { - "doctrine/coding-standard": "^12.0", - "phpbench/phpbench": "^1.0", - "phpstan/phpstan": "1.10.59", - "phpunit/phpunit": "^10.4.0", + "doctrine/annotations": "^1.13 || ^2", + "doctrine/coding-standard": "^9.0.2 || ^12.0", + "phpbench/phpbench": "^0.16.10 || ^1.0", + "phpstan/phpstan": "~1.4.10 || 1.10.59", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psr/log": "^1 || ^2 || ^3", "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4 || ^6.2 || ^7.0", - "vimeo/psalm": "5.22.2" + "symfony/cache": "^4.4 || ^5.4 || ^6.4 || ^7.0", + "symfony/var-exporter": "^4.4 || ^5.4 || ^6.2 || ^7.0", + "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0 || ^7.0", + "vimeo/psalm": "4.30.0 || 5.22.2" }, "suggest": { "ext-dom": "Provides support for XSD validation for XML mapping files", - "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0" + "symfony/cache": "Provides cache support for Setup Tool with doctrine/cache 2.0", + "symfony/yaml": "If you want to use YAML Metadata Mapping Driver" }, + "bin": [ + "bin/doctrine" + ], "type": "library", "autoload": { "psr-4": { @@ -1536,9 +1473,9 @@ ], "support": { "issues": "https://github.com/doctrine/orm/issues", - "source": "https://github.com/doctrine/orm/tree/3.1.3" + "source": "https://github.com/doctrine/orm/tree/2.19.5" }, - "time": "2024-04-30T07:14:13+00:00" + "time": "2024-04-30T06:49:54+00:00" }, { "name": "doctrine/persistence", @@ -1695,49 +1632,50 @@ }, { "name": "gedmo/doctrine-extensions", - "version": "v3.14.0", + "version": "v3.15.0", "source": { "type": "git", "url": "https://github.com/doctrine-extensions/DoctrineExtensions.git", - "reference": "3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf" + "reference": "2a89103f4984d8970f3855284c8c04e6e6a63c0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf", - "reference": "3b5b5cba476b4ae32a55ef69ef2e59d64d5893cf", + "url": "https://api.github.com/repos/doctrine-extensions/DoctrineExtensions/zipball/2a89103f4984d8970f3855284c8c04e6e6a63c0f", + "reference": "2a89103f4984d8970f3855284c8c04e6e6a63c0f", "shasum": "" }, "require": { "behat/transliterator": "^1.2", - "doctrine/annotations": "^1.13 || ^2.0", "doctrine/collections": "^1.2 || ^2.0", "doctrine/common": "^2.13 || ^3.0", + "doctrine/deprecations": "^1.0", "doctrine/event-manager": "^1.2 || ^2.0", "doctrine/persistence": "^2.2 || ^3.0", "php": "^7.4 || ^8.0", "psr/cache": "^1 || ^2 || ^3", - "symfony/cache": "^5.4 || ^6.0 || ^7.0", - "symfony/deprecation-contracts": "^2.1 || ^3.0" + "psr/clock": "^1", + "symfony/cache": "^5.4 || ^6.0 || ^7.0" }, "conflict": { - "doctrine/dbal": "<3.2", - "doctrine/mongodb-odm": "<2.3", - "doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1", - "sebastian/comparator": "<2.0" + "doctrine/annotations": "<1.13 || >=3.0", + "doctrine/dbal": "<3.2 || >=4.0", + "doctrine/mongodb-odm": "<2.3 || >=3.0", + "doctrine/orm": "<2.14.0 || 2.16.0 || 2.16.1 || >=3.0" }, "require-dev": { + "doctrine/annotations": "^1.13 || ^2.0", "doctrine/cache": "^1.11 || ^2.0", "doctrine/dbal": "^3.2", "doctrine/doctrine-bundle": "^2.3", "doctrine/mongodb-odm": "^2.3", "doctrine/orm": "^2.14.0", "friendsofphp/php-cs-fixer": "^3.14.0", - "nesbot/carbon": "^2.71 || 3.x-dev as 3.0", + "nesbot/carbon": "^2.71 || ^3.0", "phpstan/phpstan": "^1.10.2", "phpstan/phpstan-doctrine": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.6", - "rector/rector": "^0.18", + "rector/rector": "^0.19", "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/phpunit-bridge": "^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0" @@ -1797,7 +1735,7 @@ "support": { "email": "gediminas.morkevicius@gmail.com", "issues": "https://github.com/doctrine-extensions/DoctrineExtensions/issues", - "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.14.0", + "source": "https://github.com/doctrine-extensions/DoctrineExtensions/tree/v3.15.0", "wiki": "https://github.com/Atlantic18/DoctrineExtensions/tree/main/doc" }, "funding": [ @@ -1818,7 +1756,7 @@ "type": "github" } ], - "time": "2023-12-03T09:10:34+00:00" + "time": "2024-02-12T15:17:22+00:00" }, { "name": "gesdinet/jwt-refresh-token-bundle", @@ -2269,16 +2207,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.4.0", + "version": "5.4.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "298d2febfe79d03fe714eb871d5538da55205b1a" + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/298d2febfe79d03fe714eb871d5538da55205b1a", - "reference": "298d2febfe79d03fe714eb871d5538da55205b1a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", "shasum": "" }, "require": { @@ -2327,9 +2265,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" }, - "time": "2024-04-09T21:13:58+00:00" + "time": "2024-05-21T05:55:05+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -3012,21 +2950,21 @@ }, { "name": "stof/doctrine-extensions-bundle", - "version": "v1.10.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/stof/StofDoctrineExtensionsBundle.git", - "reference": "299d5333ce83941069852be36b949abbc776bf1d" + "reference": "9f7023e4c8a1c00a5627d41c1027a3f89e477630" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/299d5333ce83941069852be36b949abbc776bf1d", - "reference": "299d5333ce83941069852be36b949abbc776bf1d", + "url": "https://api.github.com/repos/stof/StofDoctrineExtensionsBundle/zipball/9f7023e4c8a1c00a5627d41c1027a3f89e477630", + "reference": "9f7023e4c8a1c00a5627d41c1027a3f89e477630", "shasum": "" }, "require": { - "gedmo/doctrine-extensions": "^3.5.0", - "php": "^7.2.5 || ^8.0", + "gedmo/doctrine-extensions": "^3.15.0", + "php": "^7.4 || ^8.0", "symfony/cache": "^5.4 || ^6.0 || ^7.0", "symfony/config": "^5.4 || ^6.0 || ^7.0", "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", @@ -3034,6 +2972,11 @@ "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpstan/phpstan-symfony": "^1.3", "symfony/mime": "^5.4 || ^6.0 || ^7.0", "symfony/phpunit-bridge": "^v6.4.1 || ^7.0.1", "symfony/security-core": "^5.4 || ^6.0 || ^7.0" @@ -3081,9 +3024,9 @@ ], "support": { "issues": "https://github.com/stof/StofDoctrineExtensionsBundle/issues", - "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.10.1" + "source": "https://github.com/stof/StofDoctrineExtensionsBundle/tree/v1.11.0" }, - "time": "2023-12-09T09:33:39+00:00" + "time": "2024-02-13T14:43:20+00:00" }, { "name": "symfony/asset", @@ -6866,16 +6809,16 @@ }, { "name": "twig/twig", - "version": "v3.10.2", + "version": "v3.10.3", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7aaed0b8311a557cc8c4047a71fd03153a00e755" + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7aaed0b8311a557cc8c4047a71fd03153a00e755", - "reference": "7aaed0b8311a557cc8c4047a71fd03153a00e755", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", "shasum": "" }, "require": { @@ -6929,7 +6872,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.10.2" + "source": "https://github.com/twigphp/Twig/tree/v3.10.3" }, "funding": [ { @@ -6941,7 +6884,7 @@ "type": "tidelift" } ], - "time": "2024-05-14T06:04:16+00:00" + "time": "2024-05-16T10:04:27+00:00" }, { "name": "webmozart/assert", diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index e1dd16e..5fdb06b 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -1,18 +1,24 @@ resources: App\Entity\User: + processor: App\State\Processor\UserProcessor + input: App\Dto\Input\UserInput + output: App\Dto\Output\UserOutput normalization_context: - groups: ['user:read'] + groups: ['default', 'user:read'] denormalization_context: - groups: ['user:write'] operations: ApiPlatform\Metadata\GetCollection: + provider: App\State\Provider\UserProvider filters: - 'api_platform.filter.user.order' - 'api_platform.filter.user.search' - ApiPlatform\Metadata\Get: ~ - ApiPlatform\Metadata\Put: ~ - ApiPlatform\Metadata\Patch: ~ + ApiPlatform\Metadata\Get: + provider: App\State\Provider\UserProvider + ApiPlatform\Metadata\Put: + provider: App\State\Provider\UserProvider + ApiPlatform\Metadata\Patch: + provider: App\State\Provider\UserProvider ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ diff --git a/config/api_platform/UserGroup.yaml b/config/api_platform/UserGroup.yaml index bb6931c..9602c01 100644 --- a/config/api_platform/UserGroup.yaml +++ b/config/api_platform/UserGroup.yaml @@ -1,7 +1,7 @@ resources: App\Entity\UserGroup: normalization_context: - groups: ['user-group:read'] + groups: ['default', 'user-group:read'] denormalization_context: groups: ['user-group:write'] operations: diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 00c8296..5efaf99 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -4,19 +4,15 @@ api_platform: version: 1.0.0 path_segment_name_generator: 'api_platform.path_segment_name_generator.dash' formats: - jsonld: ['application/ld+json'] + jsonld: ['application/ld+json', 'application/json'] mapping: - paths: ['%kernel.project_dir%/config/api_platform'] + paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto'] defaults: pagination_client_items_per_page: true docs_formats: jsonld: ['application/ld+json'] jsonopenapi: ['application/vnd.openapi+json'] html: ['text/html'] - keep_legacy_inflector: false - use_symfony_listeners: true - graphql: - enabled: true swagger: versions: [3] api_keys: diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 75ec9e8..b247cd0 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -1,13 +1,10 @@ doctrine: dbal: url: '%env(resolve:DATABASE_URL)%' - - # IMPORTANT: You MUST configure your server version, - # either here or in the DATABASE_URL env var (see .env file) - #server_version: '16' - profiling_collect_backtrace: '%kernel.debug%' use_savepoints: true + mapping_types: + enum: string orm: auto_generate_proxy_classes: true enable_lazy_ghost_objects: true diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml index b258add..1432072 100644 --- a/config/packages/stof_doctrine_extensions.yaml +++ b/config/packages/stof_doctrine_extensions.yaml @@ -1,4 +1,6 @@ -# Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html -# See the official DoctrineExtensions documentation for more details: https://github.com/doctrine-extensions/DoctrineExtensions/tree/main/doc stof_doctrine_extensions: - default_locale: en_US + default_locale: es_ES + orm: + default: + timestampable: true + blameable: true \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index 9650227..e5006be 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -24,3 +24,8 @@ services: decorates: 'api_platform.openapi.factory' arguments: [ '@App\OpenApi\OpenApiFactory.inner' ] autoconfigure: false + + App\State\Provider\UserProvider: + bind: + $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' + $itemProvider: '@api_platform.doctrine.orm.state.item_provider' diff --git a/migrations/Version20240524063952.php b/migrations/Version20240524063952.php new file mode 100644 index 0000000..f3f4231 --- /dev/null +++ b/migrations/Version20240524063952.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE organizational_unit (id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT 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, description VARCHAR(255) DEFAULT NULL, comments VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_749AEB2DD17F50A6 (uuid), INDEX IDX_749AEB2D727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D727ACA70 FOREIGN KEY (parent_id) REFERENCES organizational_unit (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D727ACA70'); + $this->addSql('DROP TABLE organizational_unit'); + } +} diff --git a/migrations/Version20240524065625.php b/migrations/Version20240524065625.php new file mode 100644 index 0000000..6941a70 --- /dev/null +++ b/migrations/Version20240524065625.php @@ -0,0 +1,38 @@ +addSql('CREATE TABLE network_settings (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, proxy VARCHAR(255) DEFAULT NULL, dns VARCHAR(255) DEFAULT NULL, netmask VARCHAR(255) DEFAULT NULL, router VARCHAR(255) DEFAULT NULL, ntp VARCHAR(255) DEFAULT NULL, p2p_time INT DEFAULT NULL, p2p_mode VARCHAR(255) DEFAULT NULL, mcast_ip VARCHAR(255) DEFAULT NULL, mcast_speed INT NOT NULL, mcast_mode VARCHAR(255) DEFAULT NULL, mcast_port INT DEFAULT NULL, UNIQUE INDEX UNIQ_48869B54D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + + $this->addSql('ALTER TABLE organizational_unit ADD network_settings_id INT DEFAULT NULL, ADD path VARCHAR(255) DEFAULT NULL'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D9B9A36D0 FOREIGN KEY (network_settings_id) REFERENCES network_settings (id)'); + $this->addSql('CREATE INDEX IDX_749AEB2D9B9A36D0 ON organizational_unit (network_settings_id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D9B9A36D0'); + $this->addSql('DROP TABLE network_settings'); + $this->addSql('DROP INDEX IDX_749AEB2D9B9A36D0 ON organizational_unit'); + $this->addSql('ALTER TABLE organizational_unit DROP network_settings_id, DROP path'); + } +} diff --git a/migrations/Version20240524083309.php b/migrations/Version20240524083309.php new file mode 100644 index 0000000..cee2d72 --- /dev/null +++ b/migrations/Version20240524083309.php @@ -0,0 +1,35 @@ +addSql('CREATE TABLE user_organizational_unit (user_id INT NOT NULL, organizational_unit_id INT NOT NULL, INDEX IDX_5E59845FA76ED395 (user_id), INDEX IDX_5E59845FFB84408A (organizational_unit_id), PRIMARY KEY(user_id, organizational_unit_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FFB84408A FOREIGN KEY (organizational_unit_id) REFERENCES organizational_unit (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FA76ED395'); + $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FFB84408A'); + $this->addSql('DROP TABLE user_organizational_unit'); + } +} diff --git a/src/Dto/Input/UserGroupInput.php b/src/Dto/Input/UserGroupInput.php new file mode 100644 index 0000000..8902d95 --- /dev/null +++ b/src/Dto/Input/UserGroupInput.php @@ -0,0 +1,32 @@ +name = $userGroup->getName(); + $this->roles = $userGroup->getRoles(); + $this->enabled= $userGroup->isEnabled(); + } +} \ No newline at end of file diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php new file mode 100644 index 0000000..45479b0 --- /dev/null +++ b/src/Dto/Input/UserInput.php @@ -0,0 +1,69 @@ +username = $user->getUsername(); + $this->roles = $user->getRoles(); + $this->enabled= $user->isEnabled(); + $this->userGroups = $user->getUserGroups()->toArray(); + $this->allowedOrganizationalUnits = $user->getAllowedOrganizationalUnits()->toArray(); + } + + public function createOrUpdateEntity(?User $user = null): User + { + if (!$user) { + $user = new User(); + } + + $user->setUsername($this->username); + $user->setRoles($this->roles); + $user->setPassword($this->password); + $user->setEnabled($this->enabled); + $user->setUserGroups($this->userGroups); + //$user->setAllowedOrganizationalUnits($this->allowedOrganizationalUnits); + + return $user; + } +} \ No newline at end of file diff --git a/src/Dto/Output/AbstractOutput.php b/src/Dto/Output/AbstractOutput.php new file mode 100644 index 0000000..b5b0c56 --- /dev/null +++ b/src/Dto/Output/AbstractOutput.php @@ -0,0 +1,25 @@ +uuid = $entity->getUuid(); + $this->id = $entity->getId(); + } +} \ No newline at end of file diff --git a/src/Dto/Output/UserOutput.php b/src/Dto/Output/UserOutput.php new file mode 100644 index 0000000..ec66afe --- /dev/null +++ b/src/Dto/Output/UserOutput.php @@ -0,0 +1,40 @@ +username = $user->getUsername(); + $this->roles = $user->getRoles(); + $this->enabled = $user->isEnabled(); + $this->allowedOrganizationalUnits = $user->getAllowedOrganizationalUnits()->toArray(); + $this->createAt = $user->getCreatedAt(); + $this->createBy = $user->getCreatedBy(); + } +} \ No newline at end of file diff --git a/src/Entity/AbstractEntity.php b/src/Entity/AbstractEntity.php index a59a32d..031fc7f 100644 --- a/src/Entity/AbstractEntity.php +++ b/src/Entity/AbstractEntity.php @@ -36,6 +36,11 @@ abstract class AbstractEntity return $this->id; } + public function getUuid(): UuidInterface + { + return $this->uuid; + } + public function getMigrationId(): ?string { return $this->migrationId; diff --git a/src/Entity/NameableTrait.php b/src/Entity/NameableTrait.php new file mode 100644 index 0000000..5510771 --- /dev/null +++ b/src/Entity/NameableTrait.php @@ -0,0 +1,23 @@ +name; + } + + public function setName(string $name): static + { + $this->name = $name; + + return $this; + } + + +} \ No newline at end of file diff --git a/src/Entity/NetworkSettings.php b/src/Entity/NetworkSettings.php new file mode 100644 index 0000000..7f36461 --- /dev/null +++ b/src/Entity/NetworkSettings.php @@ -0,0 +1,224 @@ + + */ + #[ORM\OneToMany(targetEntity: OrganizationalUnit::class, mappedBy: 'networkSettings')] + private Collection $organizationalUnits; + + public function __construct() + { + parent::__construct(); + $this->organizationalUnits = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getProxy(): ?string + { + return $this->proxy; + } + + public function setProxy(?string $proxy): static + { + $this->proxy = $proxy; + + return $this; + } + + public function getDns(): ?string + { + return $this->dns; + } + + public function setDns(?string $dns): static + { + $this->dns = $dns; + + return $this; + } + + public function getNetmask(): ?string + { + return $this->netmask; + } + + public function setNetmask(?string $netmask): static + { + $this->netmask = $netmask; + + return $this; + } + + public function getRouter(): ?string + { + return $this->router; + } + + public function setRouter(?string $router): static + { + $this->router = $router; + + return $this; + } + + public function getNtp(): ?string + { + return $this->ntp; + } + + public function setNtp(?string $ntp): static + { + $this->ntp = $ntp; + + return $this; + } + + public function getP2pTime(): ?int + { + return $this->p2pTime; + } + + public function setP2pTime(?int $p2pTime): static + { + $this->p2pTime = $p2pTime; + + return $this; + } + + public function getP2pMode(): ?string + { + return $this->p2pMode; + } + + public function setP2pMode(?string $p2pMode): static + { + $this->p2pMode = $p2pMode; + + return $this; + } + + public function getMcastIp(): ?string + { + return $this->mcastIp; + } + + public function setMcastIp(?string $mcastIp): static + { + $this->mcastIp = $mcastIp; + + return $this; + } + + public function getMcastSpeed(): ?int + { + return $this->mcastSpeed; + } + + public function setMcastSpeed(int $mcastSpeed): static + { + $this->mcastSpeed = $mcastSpeed; + + return $this; + } + + public function getMcastMode(): ?string + { + return $this->mcastMode; + } + + public function setMcastMode(?string $mcastMode): static + { + $this->mcastMode = $mcastMode; + + return $this; + } + + public function getMcastPort(): ?int + { + return $this->mcastPort; + } + + public function setMcastPort(?int $mcastPort): static + { + $this->mcastPort = $mcastPort; + + return $this; + } + + /** + * @return Collection + */ + public function getOrganizationalUnits(): Collection + { + return $this->organizationalUnits; + } + + public function addOrganizationalUnit(OrganizationalUnit $organizationalUnit): static + { + if (!$this->organizationalUnits->contains($organizationalUnit)) { + $this->organizationalUnits->add($organizationalUnit); + $organizationalUnit->setNetworkSettings($this); + } + + return $this; + } + + public function removeOrganizationalUnit(OrganizationalUnit $organizationalUnit): static + { + if ($this->organizationalUnits->removeElement($organizationalUnit)) { + // set the owning side to null (unless already changed) + if ($organizationalUnit->getNetworkSettings() === $this) { + $organizationalUnit->setNetworkSettings(null); + } + } + + return $this; + } +} diff --git a/src/Entity/OrganizationalUnit.php b/src/Entity/OrganizationalUnit.php new file mode 100644 index 0000000..6de54da --- /dev/null +++ b/src/Entity/OrganizationalUnit.php @@ -0,0 +1,169 @@ + + */ + #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + private Collection $organizationalUnits; + + #[ORM\ManyToOne(inversedBy: 'organizationalUnits')] + private ?NetworkSettings $networkSettings = null; + + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'allowedOrganizationalUnits')] + private Collection $users; + + public function __construct() + { + parent::__construct(); + $this->organizationalUnits = new ArrayCollection(); + $this->users = new ArrayCollection(); + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): static + { + $this->description = $description; + + return $this; + } + + public function getComments(): ?string + { + return $this->comments; + } + + public function setComments(?string $comments): static + { + $this->comments = $comments; + + return $this; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function setPath(?string $path): static + { + $this->path = $path; + + return $this; + } + + public function getParent(): ?self + { + return $this->parent; + } + + public function setParent(?self $parent): static + { + $this->parent = $parent; + + return $this; + } + + /** + * @return Collection + */ + public function getOrganizationalUnits(): Collection + { + return $this->organizationalUnits; + } + + public function addOrganizationalUnit(self $organizationalUnit): static + { + if (!$this->organizationalUnits->contains($organizationalUnit)) { + $this->organizationalUnits->add($organizationalUnit); + $organizationalUnit->setParent($this); + } + + return $this; + } + + public function removeOrganizationalUnit(self $organizationalUnit): static + { + if ($this->organizationalUnits->removeElement($organizationalUnit)) { + // set the owning side to null (unless already changed) + if ($organizationalUnit->getParent() === $this) { + $organizationalUnit->setParent(null); + } + } + + return $this; + } + + public function getNetworkSettings(): ?NetworkSettings + { + return $this->networkSettings; + } + + public function setNetworkSettings(?NetworkSettings $networkSettings): static + { + $this->networkSettings = $networkSettings; + + return $this; + } + + /** + * @return Collection + */ + public function getUsers(): Collection + { + return $this->users; + } + + public function addUser(User $user): static + { + if (!$this->users->contains($user)) { + $this->users->add($user); + $user->addAllowedOrganizationalUnit($this); + } + + return $this; + } + + public function removeUser(User $user): static + { + if ($this->users->removeElement($user)) { + $user->removeAllowedOrganizationalUnit($this); + } + + return $this; + } +} diff --git a/src/Entity/ToggleableTrait.php b/src/Entity/ToggleableTrait.php new file mode 100644 index 0000000..5e724da --- /dev/null +++ b/src/Entity/ToggleableTrait.php @@ -0,0 +1,21 @@ +enabled; + } + + public function setEnabled(bool $enabled): static + { + $this->enabled = $enabled; + + return $this; + } +} diff --git a/src/Entity/User.php b/src/Entity/User.php index 62daf7f..3fd9a92 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -13,6 +13,11 @@ use Symfony\Component\Security\Core\User\UserInterface; #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] class User extends AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface { + use ToggleableTrait; + + const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN'; + const ROLE_USER = 'ROLE_USER'; + #[ORM\Column(length: 180)] private ?string $username = null; @@ -34,10 +39,17 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate #[ORM\ManyToMany(targetEntity: UserGroup::class, mappedBy: 'users')] private Collection $userGroups; + /** + * @var Collection + */ + #[ORM\ManyToMany(targetEntity: OrganizationalUnit::class, inversedBy: 'users')] + private Collection $allowedOrganizationalUnits; + public function __construct() { parent::__construct(); $this->userGroups = new ArrayCollection(); + $this->allowedOrganizationalUnits = new ArrayCollection(); } public function getUsername(): ?string @@ -70,10 +82,18 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate public function getRoles(): array { $roles = $this->roles; - // guarantee every user at least has ROLE_USER - $roles[] = 'ROLE_USER'; - return array_unique($roles); + if (!array_search(self::ROLE_USER, $roles, true)){ + $roles[] = self::ROLE_USER; + } + + foreach ($this->getUserGroups() as $userGroup) { + if ($userGroup->isEnabled()){ + $roles = array_merge($roles, $userGroup->getRoles()); + } + } + + return array_merge(array_unique($roles)); } /** @@ -118,6 +138,17 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate return $this->userGroups; } + public function setUserGroups(array $userGroup): static + { + $this->userGroups->clear(); + + foreach ($userGroup as $group){ + $this->addUserGroup($group); + } + + return $this; + } + public function addUserGroup(UserGroup $userGroup): static { if (!$this->userGroups->contains($userGroup)) { @@ -136,4 +167,28 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate return $this; } + + /** + * @return Collection + */ + public function getAllowedOrganizationalUnits(): Collection + { + return $this->allowedOrganizationalUnits; + } + + public function addAllowedOrganizationalUnit(OrganizationalUnit $allowedOrganizationalUnit): static + { + if (!$this->allowedOrganizationalUnits->contains($allowedOrganizationalUnit)) { + $this->allowedOrganizationalUnits->add($allowedOrganizationalUnit); + } + + return $this; + } + + public function removeAllowedOrganizationalUnit(OrganizationalUnit $allowedOrganizationalUnit): static + { + $this->allowedOrganizationalUnits->removeElement($allowedOrganizationalUnit); + + return $this; + } } diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php index 2962b4b..b6c47c3 100644 --- a/src/Entity/UserGroup.php +++ b/src/Entity/UserGroup.php @@ -11,6 +11,8 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: UserGroupRepository::class)] class UserGroup extends AbstractEntity { + use ToggleableTrait; + #[ORM\Column(length: 255)] private ?string $name = null; diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore deleted file mode 100644 index e69de29..0000000 diff --git a/src/Repository/AbstractRepository.php b/src/Repository/AbstractRepository.php new file mode 100644 index 0000000..ff14f0b --- /dev/null +++ b/src/Repository/AbstractRepository.php @@ -0,0 +1,35 @@ +getEntityManager()->persist($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } + + public function delete (AbstractEntity $entity, bool $flush = true): void + { + $this->getEntityManager()->remove($entity); + + if ($flush) { + $this->getEntityManager()->flush(); + } + } +} \ No newline at end of file diff --git a/src/Repository/NetworkSettingsRepository.php b/src/Repository/NetworkSettingsRepository.php new file mode 100644 index 0000000..66a3251 --- /dev/null +++ b/src/Repository/NetworkSettingsRepository.php @@ -0,0 +1,43 @@ + + */ +class NetworkSettingsRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, NetworkSettings::class); + } + +// /** +// * @return NetworkSettings[] Returns an array of NetworkSettings objects +// */ +// public function findByExampleField($value): array +// { +// return $this->createQueryBuilder('n') +// ->andWhere('n.exampleField = :val') +// ->setParameter('val', $value) +// ->orderBy('n.id', 'ASC') +// ->setMaxResults(10) +// ->getQuery() +// ->getResult() +// ; +// } + +// public function findOneBySomeField($value): ?NetworkSettings +// { +// return $this->createQueryBuilder('n') +// ->andWhere('n.exampleField = :val') +// ->setParameter('val', $value) +// ->getQuery() +// ->getOneOrNullResult() +// ; +// } +} diff --git a/src/Repository/OrganizationalUnitRepository.php b/src/Repository/OrganizationalUnitRepository.php new file mode 100644 index 0000000..591f292 --- /dev/null +++ b/src/Repository/OrganizationalUnitRepository.php @@ -0,0 +1,43 @@ + + */ +class OrganizationalUnitRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, OrganizationalUnit::class); + } + + // /** + // * @return OrganizationalUnit[] Returns an array of OrganizationalUnit objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('o') + // ->andWhere('o.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('o.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?OrganizationalUnit + // { + // return $this->createQueryBuilder('o') + // ->andWhere('o.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 4f2804e..dc47b0b 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -12,7 +12,7 @@ use Symfony\Component\Security\Core\User\PasswordUpgraderInterface; /** * @extends ServiceEntityRepository */ -class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface +class UserRepository extends AbstractRepository implements PasswordUpgraderInterface { public function __construct(ManagerRegistry $registry) { @@ -32,29 +32,4 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader $this->getEntityManager()->persist($user); $this->getEntityManager()->flush(); } - - // /** - // * @return User[] Returns an array of User objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('u.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } - - // public function findOneBySomeField($value): ?User - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } } diff --git a/src/State/Processor/UserProcessor.php b/src/State/Processor/UserProcessor.php new file mode 100644 index 0000000..20077be --- /dev/null +++ b/src/State/Processor/UserProcessor.php @@ -0,0 +1,62 @@ +processCreateOrUpdate($data, $operation, $uriVariables, $context); + case $operation instanceof Delete: + return $this->processDelete($data, $operation, $uriVariables, $context); + } + } + + private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): UserOutput + { + if (!($data instanceof UserInput)) { + throw new \Exception(sprintf('data is not instance of %s', UserInput::class)); + } + + $entity = null; + if (isset($uriVariables['uuid'])) { + $entity = $this->userRepository->findOneByUuid($uriVariables['id']); + } + + $user = $data->createOrUpdateEntity($entity); + $this->validator->validate($user); + $this->userRepository->save($user); + + return new UserOutput($user); + } + + private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null + { + $user = $this->userRepository->findOneByUuid($uriVariables['id']); + $this->userRepository->delete($user); + + return null; + } +} diff --git a/src/State/Provider/UserProvider.php b/src/State/Provider/UserProvider.php new file mode 100644 index 0000000..19a7eea --- /dev/null +++ b/src/State/Provider/UserProvider.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|array|null + { + $paginator = $this->collectionProvider->provide($operation, $uriVariables, $context); + + $items = new \ArrayObject(); + foreach ($paginator->getIterator() as $item){ + $items[] = new UserOutput($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('User not found'); + } + + return new UserOutput($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 ? new UserInput($item) : null; + } + + return new UserInput(); + } +} diff --git a/symfony.lock b/symfony.lock index 956ed64..dce74d3 100644 --- a/symfony.lock +++ b/symfony.lock @@ -13,15 +13,6 @@ "src/ApiResource/.gitignore" ] }, - "doctrine/annotations": { - "version": "2.0", - "recipe": { - "repo": "github.com/symfony/recipes", - "branch": "main", - "version": "1.10", - "ref": "64d8583af5ea57b7afa4aba4b159907f3a148b05" - } - }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { From 1350735e5c6eb8f81c20a8c5db6aaa8cdb2e3b16 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Fri, 24 May 2024 13:28:55 +0200 Subject: [PATCH 04/15] refs #375. Corregidos errores con Uuid --- config/packages/api_platform.yaml | 2 ++ src/Entity/User.php | 2 ++ src/State/Processor/UserProcessor.php | 19 +++++++++++++++---- src/State/Provider/UserProvider.php | 2 +- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 5efaf99..0a641d5 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -5,6 +5,8 @@ api_platform: path_segment_name_generator: 'api_platform.path_segment_name_generator.dash' formats: jsonld: ['application/ld+json', 'application/json'] + patch_formats: + jsonld: ['application/ld+json', 'application/json'] mapping: paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto'] defaults: diff --git a/src/Entity/User.php b/src/Entity/User.php index 3fd9a92..2349dd7 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -6,11 +6,13 @@ use App\Repository\UserRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; use Symfony\Component\Security\Core\User\UserInterface; #[ORM\Entity(repositoryClass: UserRepository::class)] #[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_USERNAME', fields: ['username'])] +#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')] class User extends AbstractEntity implements UserInterface, PasswordAuthenticatedUserInterface { use ToggleableTrait; diff --git a/src/State/Processor/UserProcessor.php b/src/State/Processor/UserProcessor.php index 20077be..12abba6 100644 --- a/src/State/Processor/UserProcessor.php +++ b/src/State/Processor/UserProcessor.php @@ -12,16 +12,21 @@ use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\UserInput; use App\Dto\Output\UserOutput; use App\Repository\UserRepository; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class UserProcessor implements ProcessorInterface { public function __construct( - private UserRepository $userRepository, - private ValidatorInterface $validator + private readonly UserRepository $userRepository, + private readonly ValidatorInterface $validator, + private readonly UserPasswordHasherInterface $userPasswordHasher ) { } + /** + * @throws \Exception + */ public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) { switch ($operation){ @@ -34,6 +39,9 @@ class UserProcessor implements ProcessorInterface } } + /** + * @throws \Exception + */ private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): UserOutput { if (!($data instanceof UserInput)) { @@ -42,10 +50,13 @@ class UserProcessor implements ProcessorInterface $entity = null; if (isset($uriVariables['uuid'])) { - $entity = $this->userRepository->findOneByUuid($uriVariables['id']); + $entity = $this->userRepository->findOneByUuid($uriVariables['uuid']); } $user = $data->createOrUpdateEntity($entity); + + $user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password)); + $this->validator->validate($user); $this->userRepository->save($user); @@ -54,7 +65,7 @@ class UserProcessor implements ProcessorInterface private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null { - $user = $this->userRepository->findOneByUuid($uriVariables['id']); + $user = $this->userRepository->findOneByUuid($uriVariables['uuid']); $this->userRepository->delete($user); return null; diff --git a/src/State/Provider/UserProvider.php b/src/State/Provider/UserProvider.php index 19a7eea..f3902eb 100644 --- a/src/State/Provider/UserProvider.php +++ b/src/State/Provider/UserProvider.php @@ -63,7 +63,7 @@ class UserProvider implements ProviderInterface if (isset($uriVariables['uuid'])) { $item = $this->itemProvider->provide($operation, $uriVariables, $context); - return !$item ? new UserInput($item) : null; + return $item !== null ? new UserInput($item) : null; } return new UserInput(); From c76d0e4c7450e846f1ef8fd0982b9fde2cd9d331 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 27 May 2024 11:05:03 +0200 Subject: [PATCH 05/15] refs #377. Creacion de test funcionales para API User --- .env.test | 6 + .gitignore | 10 + bin/phpunit | 23 + composer.json | 6 + composer.lock | 2200 ++++++++++++++++- config/api_platform/User.yaml | 4 +- config/bundles.php | 1 + config/packages/api_platform.yaml | 7 + .../packages/dama_doctrine_test_bundle.yaml | 5 + phpunit.xml.dist | 39 + src/Dto/Input/UserInput.php | 10 +- src/Factory/UserFactory.php | 5 - src/State/Processor/UserProcessor.php | 6 +- symfony.lock | 41 + tests/Functional/AbstractTest.php | 73 + tests/Functional/UserTest.php | 115 + tests/bootstrap.php | 13 + 17 files changed, 2552 insertions(+), 12 deletions(-) create mode 100644 .env.test create mode 100755 bin/phpunit create mode 100644 config/packages/dama_doctrine_test_bundle.yaml create mode 100644 phpunit.xml.dist create mode 100644 tests/Functional/AbstractTest.php create mode 100644 tests/Functional/UserTest.php create mode 100644 tests/bootstrap.php diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9e7162f --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/.gitignore b/.gitignore index 19d00a8..2574338 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,13 @@ ###> lexik/jwt-authentication-bundle ### /config/jwt/*.pem ###< lexik/jwt-authentication-bundle ### + +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### + +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### diff --git a/bin/phpunit b/bin/phpunit new file mode 100755 index 0000000..692bacc --- /dev/null +++ b/bin/phpunit @@ -0,0 +1,23 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/composer.json b/composer.json index 3312f07..4a2dab4 100644 --- a/composer.json +++ b/composer.json @@ -83,8 +83,14 @@ } }, "require-dev": { + "dama/doctrine-test-bundle": "^8.1", "doctrine/doctrine-fixtures-bundle": "^3.6", + "phpunit/phpunit": "^9.5", + "symfony/browser-kit": "6.4.*", + "symfony/css-selector": "6.4.*", + "symfony/http-client": "6.4.*", "symfony/maker-bundle": "^1.59", + "symfony/phpunit-bridge": "^7.0", "symfony/web-profiler-bundle": "^6.4", "zenstruck/foundry": "^1.37" } diff --git a/composer.lock b/composer.lock index ef16494..2b0b43f 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": "a03ea4d76774164d557cb2ecb327de33", + "content-hash": "e953b0066fb773aaf6b3835086280c86", "packages": [ { "name": "api-platform/core", @@ -7002,6 +7002,73 @@ } ], "packages-dev": [ + { + "name": "dama/doctrine-test-bundle", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/dmaicher/doctrine-test-bundle.git", + "reference": "21b4dd73546991c7df34ba92ecbf305a1ae5a0ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/21b4dd73546991c7df34ba92ecbf305a1ae5a0ee", + "reference": "21b4dd73546991c7df34ba92ecbf305a1ae5a0ee", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.3 || ^4.0", + "doctrine/doctrine-bundle": "^2.2.2", + "php": "^7.4 || ^8.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "symfony/cache": "^5.4 || ^6.3 || ^7.0", + "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0" + }, + "require-dev": { + "behat/behat": "^3.0", + "friendsofphp/php-cs-fixer": "^3.27", + "phpstan/phpstan": "^1.2", + "phpunit/phpunit": "^8.0 || ^9.0 || ^10.0 || ^11.0", + "symfony/phpunit-bridge": "^6.3", + "symfony/process": "^5.4 || ^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.3 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "8.x-dev" + } + }, + "autoload": { + "psr-4": { + "DAMA\\DoctrineTestBundle\\": "src/DAMA/DoctrineTestBundle" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Maicher", + "email": "mail@dmaicher.de" + } + ], + "description": "Symfony bundle to isolate doctrine database tests and improve test performance", + "keywords": [ + "doctrine", + "isolation", + "performance", + "symfony", + "testing", + "tests" + ], + "support": { + "issues": "https://github.com/dmaicher/doctrine-test-bundle/issues", + "source": "https://github.com/dmaicher/doctrine-test-bundle/tree/v8.1.0" + }, + "time": "2024-05-21T18:06:21+00:00" + }, { "name": "doctrine/data-fixtures", "version": "1.7.0", @@ -7236,6 +7303,132 @@ }, "time": "2024-01-02T13:46:09+00:00" }, + { + "name": "masterminds/html5", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.9.0" + }, + "time": "2024-03-31T07:05:07+00:00" + }, + { + "name": "myclabs/deep-copy", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3,<3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2023-03-08T13:26:56+00:00" + }, { "name": "nikic/php-parser", "version": "v5.0.2", @@ -7294,6 +7487,1880 @@ }, "time": "2024-03-05T20:51:40+00:00" }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "9.2.31", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965", + "reference": "48c34b5d8d983006bd2adc2d0de92963b9155965", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3", + "phpunit/php-file-iterator": "^3.0.3", + "phpunit/php-text-template": "^2.0.2", + "sebastian/code-unit-reverse-lookup": "^2.0.2", + "sebastian/complexity": "^2.0", + "sebastian/environment": "^5.1.2", + "sebastian/lines-of-code": "^1.0.3", + "sebastian/version": "^3.0.1", + "theseer/tokenizer": "^1.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:37:42+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "3.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2021-12-02T12:48:52+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "3.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:58:55+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T05:33:50+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "5.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:16:10+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "9.6.19", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.3.1 || ^2", + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.10.1", + "phar-io/manifest": "^2.0.3", + "phar-io/version": "^3.0.2", + "php": ">=7.3", + "phpunit/php-code-coverage": "^9.2.28", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-invoker": "^3.1.1", + "phpunit/php-text-template": "^2.0.3", + "phpunit/php-timer": "^5.0.2", + "sebastian/cli-parser": "^1.0.1", + "sebastian/code-unit": "^1.0.6", + "sebastian/comparator": "^4.0.8", + "sebastian/diff": "^4.0.3", + "sebastian/environment": "^5.1.3", + "sebastian/exporter": "^4.0.5", + "sebastian/global-state": "^5.0.1", + "sebastian/object-enumerator": "^4.0.3", + "sebastian/resource-operations": "^3.0.3", + "sebastian/type": "^3.2", + "sebastian/version": "^3.0.2" + }, + "suggest": { + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "9.6-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsors.html", + "type": "custom" + }, + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" + } + ], + "time": "2024-04-05T04:35:58+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:27:43+00:00" + }, + { + "name": "sebastian/code-unit", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", + "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit/issues", + "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:08:54+00:00" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T05:30:19+00:00" + }, + { + "name": "sebastian/comparator", + "version": "4.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", + "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/diff": "^4.0", + "sebastian/exporter": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2022-09-14T12:41:17+00:00" + }, + { + "name": "sebastian/complexity", + "version": "2.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:19:30+00:00" + }, + { + "name": "sebastian/diff", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc", + "reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:30:58+00:00" + }, + { + "name": "sebastian/environment", + "version": "5.1.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:03:51+00:00" + }, + { + "name": "sebastian/exporter", + "version": "4.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72", + "reference": "78c00df8f170e02473b682df15bfcdacc3d32d72", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:33:00+00:00" + }, + { + "name": "sebastian/global-state", + "version": "5.0.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^9.3" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T06:35:11+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "1.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.18 || ^5.0", + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-12-22T06:20:34+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "4.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", + "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "shasum": "" + }, + "require": { + "php": ">=7.3", + "sebastian/object-reflector": "^2.0", + "sebastian/recursion-context": "^4.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:12:34+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-10-26T13:14:26+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:07:39+00:00" + }, + { + "name": "sebastian/resource-operations", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-14T16:00:52+00:00" + }, + { + "name": "sebastian/type", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-02-03T06:13:03+00:00" + }, + { + "name": "sebastian/version", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c6c1022351a901512170118436c764e473f6de8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", + "reference": "c6c1022351a901512170118436c764e473f6de8c", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2020-09-28T06:39:44+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v6.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "c276856598f70e96f75403fc04841cec1dc56e74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/c276856598f70e96f75403fc04841cec1dc56e74", + "reference": "c276856598f70e96f75403fc04841cec1dc56e74", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/dom-crawler": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/mime": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "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": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/browser-kit/tree/v6.4.7" + }, + "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-04-18T09:22:46+00:00" + }, + { + "name": "symfony/css-selector", + "version": "v6.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/css-selector.git", + "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c5d5c2103c3762aff27a27e1e2409e30a79083b", + "reference": "1c5d5c2103c3762aff27a27e1e2409e30a79083b", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Converts CSS selectors to XPath expressions", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/css-selector/tree/v6.4.7" + }, + "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-04-18T09:22:46+00:00" + }, + { + "name": "symfony/dom-crawler", + "version": "v6.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "2088c5da700b1e7a8689fffc10dda6c1f643deea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/2088c5da700b1e7a8689fffc10dda6c1f643deea", + "reference": "2088c5da700b1e7a8689fffc10dda6c1f643deea", + "shasum": "" + }, + "require": { + "masterminds/html5": "^2.6", + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" + }, + "require-dev": { + "symfony/css-selector": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\DomCrawler\\": "" + }, + "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": "Eases DOM navigation for HTML and XML documents", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dom-crawler/tree/v6.4.7" + }, + "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-04-18T09:22:46+00:00" + }, + { + "name": "symfony/http-client", + "version": "v6.4.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/3683d8107cf1efdd24795cc5f7482be1eded34ac", + "reference": "3683d8107cf1efdd24795cc5f7482be1eded34ac", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "^3.4.1", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.4.7" + }, + "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-04-18T09:22:46+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "20414d96f391677bf80078aa55baece78b82647d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", + "reference": "20414d96f391677bf80078aa55baece78b82647d", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + }, + "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-04-18T09:32:20+00:00" + }, { "name": "symfony/maker-bundle", "version": "v1.59.1", @@ -7386,6 +9453,87 @@ ], "time": "2024-05-06T03:59:59+00:00" }, + { + "name": "symfony/phpunit-bridge", + "version": "v7.0.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/phpunit-bridge.git", + "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", + "reference": "0a0b90ba08b9a03e09ad49f8d613bdf3eca3a7a9", + "shasum": "" + }, + "require": { + "php": ">=7.2.5" + }, + "conflict": { + "phpunit/phpunit": "<7.5|9.1.2" + }, + "require-dev": { + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/error-handler": "^5.4|^6.4|^7.0", + "symfony/polyfill-php81": "^1.27" + }, + "bin": [ + "bin/simple-phpunit" + ], + "type": "symfony-bridge", + "extra": { + "thanks": { + "name": "phpunit/phpunit", + "url": "https://github.com/sebastianbergmann/phpunit" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Bridge\\PhpUnit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides utilities for PHPUnit, especially user deprecation notices management", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/phpunit-bridge/tree/v7.0.7" + }, + "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-04-18T09:29:19+00:00" + }, { "name": "symfony/web-profiler-bundle", "version": "v6.4.7", @@ -7468,6 +9616,56 @@ ], "time": "2024-04-18T09:22:46+00:00" }, + { + "name": "theseer/tokenizer", + "version": "1.2.3", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:36:25+00:00" + }, { "name": "zenstruck/assert", "version": "v1.5.0", diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index 5fdb06b..f71e14b 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -19,7 +19,9 @@ resources: provider: App\State\Provider\UserProvider ApiPlatform\Metadata\Patch: provider: App\State\Provider\UserProvider - ApiPlatform\Metadata\Post: ~ + ApiPlatform\Metadata\Post: + validation_context: + groups: [ 'default', 'user:post' ] ApiPlatform\Metadata\Delete: ~ properties: diff --git a/config/bundles.php b/config/bundles.php index 4aa19fe..826d993 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,4 +15,5 @@ return [ Gesdinet\JWTRefreshTokenBundle\GesdinetJWTRefreshTokenBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true], + DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], ]; diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 0a641d5..160cfd2 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -11,6 +11,13 @@ api_platform: paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto'] defaults: pagination_client_items_per_page: true + cache_headers: + vary: [ 'Content-Type', 'Authorization', 'Origin' ] + extra_properties: + standard_put: true + rfc_7807_compliant_errors: true + event_listeners_backward_compatibility_layer: false + keep_legacy_inflector: false docs_formats: jsonld: ['application/ld+json'] jsonopenapi: ['application/vnd.openapi+json'] diff --git a/config/packages/dama_doctrine_test_bundle.yaml b/config/packages/dama_doctrine_test_bundle.yaml new file mode 100644 index 0000000..3482cba --- /dev/null +++ b/config/packages/dama_doctrine_test_bundle.yaml @@ -0,0 +1,5 @@ +when@test: + dama_doctrine_test: + enable_static_connection: true + enable_static_meta_data_cache: true + enable_static_query_cache: true diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..f6da34e --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + tests + + + + + + src + + + + + + + + + + + diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index 45479b0..6b7c2af 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -23,10 +23,10 @@ final class UserInput #[Groups(['user:write'])] public array $allowedOrganizationalUnits = []; - #[Assert\NotBlank] + #[Assert\NotBlank(groups: ['user:create'])] #[Assert\Length(min: 8)] #[Groups(['user:write'])] - public string $password; + public ?string $password = null; #[Assert\NotNull] #[Groups(['user:write'])] @@ -59,9 +59,13 @@ final class UserInput $user->setUsername($this->username); $user->setRoles($this->roles); - $user->setPassword($this->password); $user->setEnabled($this->enabled); $user->setUserGroups($this->userGroups); + + if ($this->password !== null) { + $user->setPassword($this->password); + } + //$user->setAllowedOrganizationalUnits($this->allowedOrganizationalUnits); return $user; diff --git a/src/Factory/UserFactory.php b/src/Factory/UserFactory.php index 0e41c60..d626634 100644 --- a/src/Factory/UserFactory.php +++ b/src/Factory/UserFactory.php @@ -16,11 +16,6 @@ final class UserFactory extends ModelFactory { CONST PLAIN_PASSWORD = '12345678'; - /** - * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services - * - * @todo inject services if required - */ public function __construct() { parent::__construct(); diff --git a/src/State/Processor/UserProcessor.php b/src/State/Processor/UserProcessor.php index 12abba6..fec80b5 100644 --- a/src/State/Processor/UserProcessor.php +++ b/src/State/Processor/UserProcessor.php @@ -27,7 +27,7 @@ class UserProcessor implements ProcessorInterface /** * @throws \Exception */ - public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []) + public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): UserOutput|null { switch ($operation){ case $operation instanceof Post: @@ -55,7 +55,9 @@ class UserProcessor implements ProcessorInterface $user = $data->createOrUpdateEntity($entity); - $user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password)); + if ($data->password !== null){ + $user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password)); + } $this->validator->validate($user); $this->userRepository->save($user); diff --git a/symfony.lock b/symfony.lock index dce74d3..8640694 100644 --- a/symfony.lock +++ b/symfony.lock @@ -13,6 +13,18 @@ "src/ApiResource/.gitignore" ] }, + "dama/doctrine-test-bundle": { + "version": "8.1", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "7.2", + "ref": "896306d79d4ee143af9eadf9b09fd34a8c391b70" + }, + "files": [ + "config/packages/dama_doctrine_test_bundle.yaml" + ] + }, "doctrine/doctrine-bundle": { "version": "2.12", "recipe": { @@ -90,6 +102,20 @@ "config/packages/nelmio_cors.yaml" ] }, + "phpunit/phpunit": { + "version": "9.6", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "9.6", + "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + }, + "files": [ + ".env.test", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, "ramsey/uuid-doctrine": { "version": "2.0", "recipe": { @@ -166,6 +192,21 @@ "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" } }, + "symfony/phpunit-bridge": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.3", + "ref": "a411a0480041243d97382cac7984f7dce7813c08" + }, + "files": [ + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, "symfony/routing": { "version": "6.4", "recipe": { diff --git a/tests/Functional/AbstractTest.php b/tests/Functional/AbstractTest.php new file mode 100644 index 0000000..956517f --- /dev/null +++ b/tests/Functional/AbstractTest.php @@ -0,0 +1,73 @@ +token) { + return $this->token; + } + + $response = static::createClient()->request('POST', '/auth/login', ['json' => $body ?: [ + 'username' => 'ogadmin', + 'password' => '12345678', + ]]); + + $this->assertResponseIsSuccessful(); + $data = $response->toArray(); + $this->token = $data['token']; + + return $data['token']; + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + protected function createClientWithCredentials($token = null): Client + { + $token = $token ?: $this->getToken(); + + return static::createClient([], ['headers' => ['authorization' => 'Bearer '.$token]]); + } +} \ No newline at end of file diff --git a/tests/Functional/UserTest.php b/tests/Functional/UserTest.php new file mode 100644 index 0000000..8b83430 --- /dev/null +++ b/tests/Functional/UserTest.php @@ -0,0 +1,115 @@ + self::USER_ADMIN]); + UserFactory::createMany(10); + + $this->createClientWithCredentials()->request('GET', '/api/users'); + $this->assertResponseStatusCodeSame(Response::HTTP_OK); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/api/contexts/User', + '@id' => '/api/users', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 11, + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testCreate(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + $this->createClientWithCredentials()->request('POST', '/api/users',['json' => [ + 'username' => self::USER_CREATE, + 'password' => '12345678', + 'enabled' => true, + ]]); + + $this->assertResponseStatusCodeSame(201); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/api/contexts/UserOutput', + '@type' => 'User', + 'username' => self::USER_CREATE, + 'enabled' => true, + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testUpdateBook(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_UPDATE]); + + $iri = $this->findIriBy(User::class, ['username' => self::USER_UPDATE]); + + $this->createClientWithCredentials()->request('PATCH', $iri, ['json' => [ + 'username' => self::USER_UPDATE, + ]]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@id' => $iri, + 'username' => self::USER_UPDATE, + ]); + } + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + public function testDeleteBook(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_DELETE]); + + $iri = $this->findIriBy(User::class, ['username' => self::USER_DELETE]); + + $this->createClientWithCredentials()->request('DELETE', $iri); + $this->assertResponseStatusCodeSame(204); + $this->assertNull( + static::getContainer()->get('doctrine')->getRepository(User::class)->findOneBy(['username' => self::USER_DELETE]) + ); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..47a5855 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,13 @@ +bootEnv(dirname(__DIR__).'/.env'); +} + +if ($_SERVER['APP_DEBUG']) { + umask(0000); +} From e3664f4fd0b9518ed16eb1c9fe62c82cf68e9570 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 27 May 2024 11:35:46 +0200 Subject: [PATCH 06/15] refs #377. Edit readme --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7f9feaf..385f4db 100644 --- a/README.md +++ b/README.md @@ -61,3 +61,11 @@ docker exec ogcore-php php bin/consoledoctrine:migrations:migrate --no-interacti ```sh docker exec ogcore-php php bin/console doctrine:fixtures:load --no-interaction ``` + +## Test + +Para ejecutar los test, ejecutamos el siguiente comando: + +```sh +docker compose exec php bin/phpunit +``` From 5b4248bfda73ae4e96a381eeb751467f38790756 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 27 May 2024 15:55:41 +0200 Subject: [PATCH 07/15] refs #377. Test UserGroups --- config/api_platform/User.yaml | 1 + config/api_platform/UserGroup.yaml | 14 ++- config/services.yaml | 6 + config/services/api_platform.yaml | 12 ++ migrations/Version20240527103936.php | 33 +++++ src/Command/LoadDefaultUserGroupsCommand.php | 58 +++++++++ src/DataFixtures/AppFixtures.php | 23 ++++ src/Dto/Input/UserGroupInput.php | 25 +++- src/Dto/Input/UserInput.php | 6 +- src/Dto/Output/AbstractOutput.php | 7 +- src/Dto/Output/UserGroupOutput.php | 38 ++++++ src/Entity/UserGroup.php | 13 +- src/Factory/UserGroupFactory.php | 45 +++++++ src/Repository/UserGroupRepository.php | 27 +--- src/State/Processor/UserGroupProcessor.php | 69 ++++++++++ src/State/Provider/UserGroupProvider.php | 71 +++++++++++ tests/Functional/UserGroupTest.php | 125 +++++++++++++++++++ tests/Functional/UserTest.php | 14 +-- 18 files changed, 538 insertions(+), 49 deletions(-) create mode 100644 migrations/Version20240527103936.php create mode 100644 src/Command/LoadDefaultUserGroupsCommand.php create mode 100644 src/Dto/Output/UserGroupOutput.php create mode 100644 src/Factory/UserGroupFactory.php create mode 100644 src/State/Processor/UserGroupProcessor.php create mode 100644 src/State/Provider/UserGroupProvider.php create mode 100644 tests/Functional/UserGroupTest.php diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index f71e14b..8a75827 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -13,6 +13,7 @@ resources: filters: - 'api_platform.filter.user.order' - 'api_platform.filter.user.search' + - 'api_platform.filter.user.boolean' ApiPlatform\Metadata\Get: provider: App\State\Provider\UserProvider ApiPlatform\Metadata\Put: diff --git a/config/api_platform/UserGroup.yaml b/config/api_platform/UserGroup.yaml index 9602c01..0e25cb8 100644 --- a/config/api_platform/UserGroup.yaml +++ b/config/api_platform/UserGroup.yaml @@ -1,17 +1,25 @@ resources: App\Entity\UserGroup: + processor: App\State\Processor\UserGroupProcessor + input: App\Dto\Input\UserGroupInput + output: App\Dto\Output\UserGroupOutput normalization_context: groups: ['default', 'user-group:read'] denormalization_context: groups: ['user-group:write'] operations: ApiPlatform\Metadata\GetCollection: + provider: App\State\Provider\UserGroupProvider filters: - 'api_platform.filter.user_group.order' - 'api_platform.filter.user_group.search' - ApiPlatform\Metadata\Get: ~ - ApiPlatform\Metadata\Put: ~ - ApiPlatform\Metadata\Patch: ~ + - 'api_platform.filter.user_group.boolean' + ApiPlatform\Metadata\Get: + provider: App\State\Provider\UserGroupProvider + ApiPlatform\Metadata\Put: + provider: App\State\Provider\UserGroupProvider + ApiPlatform\Metadata\Patch: + provider: App\State\Provider\UserGroupProvider ApiPlatform\Metadata\Post: ~ ApiPlatform\Metadata\Delete: ~ diff --git a/config/services.yaml b/config/services.yaml index e5006be..af7a6a0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -29,3 +29,9 @@ services: bind: $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' $itemProvider: '@api_platform.doctrine.orm.state.item_provider' + + App\State\Provider\UserGroupProvider: + bind: + $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' + $itemProvider: '@api_platform.doctrine.orm.state.item_provider' + diff --git a/config/services/api_platform.yaml b/config/services/api_platform.yaml index f735df2..8049096 100644 --- a/config/services/api_platform.yaml +++ b/config/services/api_platform.yaml @@ -13,6 +13,12 @@ services: tags: - ['api_platform.filter' ] + api_platform.filter.user.boolean: + parent: 'api_platform.doctrine.orm.boolean_filter' + arguments: [ { 'enabled': ~ } ] + tags: + - [ 'api_platform.filter' ] + api_platform.filter.user_group.order: parent: 'api_platform.doctrine.orm.order_filter' arguments: @@ -24,5 +30,11 @@ services: api_platform.filter.user_group.search: parent: 'api_platform.doctrine.orm.search_filter' arguments: [ { 'id': 'exact', 'name': 'partial' } ] + tags: + - [ 'api_platform.filter' ] + + api_platform.filter.user_group.boolean: + parent: 'api_platform.doctrine.orm.boolean_filter' + arguments: [ { 'enabled': ~ } ] tags: - [ 'api_platform.filter' ] \ No newline at end of file diff --git a/migrations/Version20240527103936.php b/migrations/Version20240527103936.php new file mode 100644 index 0000000..802e375 --- /dev/null +++ b/migrations/Version20240527103936.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE user_group CHANGE roles permissions JSON NOT NULL COMMENT \'(DC2Type:json)\''); + $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_NAME ON user_group (name)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP INDEX UNIQ_IDENTIFIER_NAME ON user_group'); + $this->addSql('ALTER TABLE user_group CHANGE permissions roles JSON NOT NULL COMMENT \'(DC2Type:json)\''); + } +} diff --git a/src/Command/LoadDefaultUserGroupsCommand.php b/src/Command/LoadDefaultUserGroupsCommand.php new file mode 100644 index 0000000..a4bdda2 --- /dev/null +++ b/src/Command/LoadDefaultUserGroupsCommand.php @@ -0,0 +1,58 @@ + 'Super Admin', + 'permissions' => ['ROLE_SUPER_ADMIN'], + 'enabled' => true + ], + [ + 'name' => 'Administrador de aulas', + 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_ADMIN'], + 'enabled' => true + ], + [ + 'name' => 'Operador de aulas', + 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_OPERATOR'], + 'enabled' => true + ], + [ + 'name' => 'Usuario', + 'permissions' => ['ROLE_USER'], + 'enabled' => true + ], + ]; + + foreach ($userGroups as $userGroup) { + $entity = new UserGroup(); + $entity->setName($userGroup['name']); + $entity->setPermissions($userGroup['permissions']); + $entity->setEnabled($userGroup['enabled']); + + $this->entityManager->persist($entity); + } + + $this->entityManager->flush(); + + return 1; + } +} \ No newline at end of file diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 8a74d9b..ded5655 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -5,13 +5,36 @@ namespace App\DataFixtures; use App\Factory\UserFactory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\HttpKernel\KernelInterface; class AppFixtures extends Fixture { CONST ADMIN_USER = 'ogadmin'; + + public function __construct( + private readonly KernelInterface $kernel, + ) + { + } + + /** + * @throws \Exception + */ public function load(ObjectManager $manager): void { UserFactory::createOne(['username' => self::ADMIN_USER]); + + $application = new Application($this->kernel); + + $input = new ArrayInput([ + 'command' => 'app:load-default-user-groups' + ]); + + $output = new BufferedOutput(); + $application->run($input, $output); } } diff --git a/src/Dto/Input/UserGroupInput.php b/src/Dto/Input/UserGroupInput.php index 8902d95..d26ef2a 100644 --- a/src/Dto/Input/UserGroupInput.php +++ b/src/Dto/Input/UserGroupInput.php @@ -13,20 +13,33 @@ final class UserGroupInput public ?string $name = null; #[Groups(['user-group:write'])] - public array $roles = []; + public array $permissions = []; #[Assert\NotNull] #[Groups(['user-group:write'])] public ?bool $enabled = false; - public function __construct(?UserGroup $userGroup = null) + public function __construct(?UserGroup $userGroupGroup = null) { - if (!$userGroup) { + if (!$userGroupGroup) { return; } - $this->name = $userGroup->getName(); - $this->roles = $userGroup->getRoles(); - $this->enabled= $userGroup->isEnabled(); + $this->name = $userGroupGroup->getName(); + $this->permissions = $userGroupGroup->getPermissions(); + $this->enabled= $userGroupGroup->isEnabled(); + } + + public function createOrUpdateEntity(?UserGroup $userGroup = null): UserGroup + { + if (!$userGroup) { + $userGroup = new UserGroup(); + } + + $userGroup->setName($this->name); + $userGroup->setPermissions($this->permissions); + $userGroup->setEnabled($this->enabled); + + return $userGroup; } } \ No newline at end of file diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index 6b7c2af..ce9e9a4 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -60,7 +60,11 @@ final class UserInput $user->setUsername($this->username); $user->setRoles($this->roles); $user->setEnabled($this->enabled); - $user->setUserGroups($this->userGroups); + + foreach ($this->userGroups as $userGroup) { + $userGroupsToAdd[] = $userGroup->getEntity(); + } + $user->setUserGroups( $userGroupsToAdd ?? [] ); if ($this->password !== null) { $user->setPassword($this->password); diff --git a/src/Dto/Output/AbstractOutput.php b/src/Dto/Output/AbstractOutput.php index b5b0c56..2ca73e0 100644 --- a/src/Dto/Output/AbstractOutput.php +++ b/src/Dto/Output/AbstractOutput.php @@ -17,9 +17,14 @@ abstract class AbstractOutput #[Groups(['default'])] public int $id; - public function __construct(AbstractEntity $entity) + public function __construct(private readonly AbstractEntity $entity) { $this->uuid = $entity->getUuid(); $this->id = $entity->getId(); } + + public function getEntity(): AbstractEntity + { + return $this->entity; + } } \ No newline at end of file diff --git a/src/Dto/Output/UserGroupOutput.php b/src/Dto/Output/UserGroupOutput.php new file mode 100644 index 0000000..1b1308d --- /dev/null +++ b/src/Dto/Output/UserGroupOutput.php @@ -0,0 +1,38 @@ +name = $userGroup->getName(); + $this->permissions = $userGroup->getPermissions(); + $this->enabled = $userGroup->isEnabled(); + $this->createAt = $userGroup->getCreatedAt(); + $this->createBy = $userGroup->getCreatedBy(); + } +} \ No newline at end of file diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php index b6c47c3..32c8ce4 100644 --- a/src/Entity/UserGroup.php +++ b/src/Entity/UserGroup.php @@ -7,8 +7,11 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[ORM\Entity(repositoryClass: UserGroupRepository::class)] +#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_NAME', fields: ['name'])] +#[UniqueEntity(fields: ['name'], message: 'There is already an role with this name')] class UserGroup extends AbstractEntity { use ToggleableTrait; @@ -17,7 +20,7 @@ class UserGroup extends AbstractEntity private ?string $name = null; #[ORM\Column(type: Types::JSON)] - private array $roles = []; + private array $permissions = []; /** * @var Collection @@ -44,14 +47,14 @@ class UserGroup extends AbstractEntity return $this; } - public function getRoles(): array + public function getPermissions(): array { - return $this->roles; + return $this->permissions; } - public function setRoles(array $roles): static + public function setPermissions(array $permissions): static { - $this->roles = $roles; + $this->permissions = $permissions; return $this; } diff --git a/src/Factory/UserGroupFactory.php b/src/Factory/UserGroupFactory.php new file mode 100644 index 0000000..30df222 --- /dev/null +++ b/src/Factory/UserGroupFactory.php @@ -0,0 +1,45 @@ + + */ +final class UserGroupFactory extends ModelFactory +{ + public function __construct() + { + parent::__construct(); + } + + + protected function getDefaults(): array + { + return [ + 'createdAt' => self::faker()->dateTime(), + 'permissions' => [], + 'updatedAt' => self::faker()->dateTime(), + ]; + } + + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization + */ + protected function initialize(): self + { + return $this + // ->afterInstantiate(function(UserGroup $userGroup): void {}) + ; + } + + protected static function getClass(): string + { + return UserGroup::class; + } +} diff --git a/src/Repository/UserGroupRepository.php b/src/Repository/UserGroupRepository.php index 33ef735..64fec99 100644 --- a/src/Repository/UserGroupRepository.php +++ b/src/Repository/UserGroupRepository.php @@ -9,35 +9,10 @@ use Doctrine\Persistence\ManagerRegistry; /** * @extends ServiceEntityRepository */ -class UserGroupRepository extends ServiceEntityRepository +class UserGroupRepository extends AbstractRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, UserGroup::class); } - - // /** - // * @return UserGroup[] Returns an array of UserGroup objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('u.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } - - // public function findOneBySomeField($value): ?UserGroup - // { - // return $this->createQueryBuilder('u') - // ->andWhere('u.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } } diff --git a/src/State/Processor/UserGroupProcessor.php b/src/State/Processor/UserGroupProcessor.php new file mode 100644 index 0000000..0dfdaea --- /dev/null +++ b/src/State/Processor/UserGroupProcessor.php @@ -0,0 +1,69 @@ +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 = []): UserGroupOutput + { + if (!($data instanceof UserGroupInput)) { + throw new \Exception(sprintf('data is not instance of %s', UserGroupInput::class)); + } + + $entity = null; + if (isset($uriVariables['uuid'])) { + $entity = $this->userGroupRepository->findOneByUuid($uriVariables['uuid']); + } + + $userGroup = $data->createOrUpdateEntity($entity); + $this->validator->validate($userGroup); + $this->userGroupRepository->save($userGroup); + + return new UserGroupOutput($userGroup); + } + + private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null + { + $user = $this->userGroupRepository->findOneByUuid($uriVariables['uuid']); + $this->userGroupRepository->delete($user); + + return null; + } +} diff --git a/src/State/Provider/UserGroupProvider.php b/src/State/Provider/UserGroupProvider.php new file mode 100644 index 0000000..2bf73a9 --- /dev/null +++ b/src/State/Provider/UserGroupProvider.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|array|null + { + $paginator = $this->collectionProvider->provide($operation, $uriVariables, $context); + + $items = new \ArrayObject(); + foreach ($paginator->getIterator() as $item){ + $items[] = new UserGroupOutput($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('User Group not found'); + } + + return new UserGroupOutput($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 UserGroupInput($item) : null; + } + + return new UserGroupInput(); + } +} diff --git a/tests/Functional/UserGroupTest.php b/tests/Functional/UserGroupTest.php new file mode 100644 index 0000000..5806634 --- /dev/null +++ b/tests/Functional/UserGroupTest.php @@ -0,0 +1,125 @@ + self::USER_ADMIN]); + + UserGroupFactory::createOne(['name' => 'Super Admin', 'permissions' => ['ROLE_SUPER_ADMIN'], 'enabled' => true]); + UserGroupFactory::createOne(['name' => 'Administrador de aulas', 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_ADMIN'], 'enabled' => true]); + UserGroupFactory::createOne(['name' => 'Operador de aulas', 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_OPERATOR'], 'enabled' => true]); + UserGroupFactory::createOne(['name' => 'Usuario', 'permissions' => ['ROLE_USER'], 'enabled' => true]); + + $this->createClientWithCredentials()->request('GET', '/api/user-groups'); + $this->assertResponseStatusCodeSame(Response::HTTP_OK); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/api/contexts/UserGroup', + '@id' => '/api/user-groups', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 4, + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testCreateUserGroup(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + $this->createClientWithCredentials()->request('POST', '/api/user-groups',['json' => [ + 'name' => self::USER_GROUP_CREATE, + 'enabled' => true, + ]]); + + $this->assertResponseStatusCodeSame(201); + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + $this->assertJsonContains([ + '@context' => '/api/contexts/UserGroupOutput', + '@type' => 'UserGroup', + 'name' => self::USER_GROUP_CREATE, + 'enabled' => true, + ]); + } + + /** + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + */ + public function testUpdateUserGroup(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + + UserGroupFactory::createOne(['name' => self::USER_GROUP_UPDATE]); + $iri = $this->findIriBy(UserGroup::class, ['name' => self::USER_GROUP_UPDATE]); + + $this->createClientWithCredentials()->request('PATCH', $iri, ['json' => [ + 'name' => self::USER_GROUP_UPDATE, + 'enabled' => false + ]]); + + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@id' => $iri, + 'name' => self::USER_GROUP_UPDATE, + 'enabled' => false + ]); + } + + + /** + * @throws TransportExceptionInterface + * @throws ServerExceptionInterface + * @throws RedirectionExceptionInterface + * @throws DecodingExceptionInterface + * @throws ClientExceptionInterface + */ + public function testDeleteUser(): void + { + UserFactory::createOne(['username' => self::USER_ADMIN]); + UserGroupFactory::createOne(['name' => self::USER_GROUP_DELETE]); + + $iri = $this->findIriBy(UserGroup::class, ['name' => self::USER_GROUP_DELETE]); + + $this->createClientWithCredentials()->request('DELETE', $iri); + $this->assertResponseStatusCodeSame(204); + $this->assertNull( + static::getContainer()->get('doctrine')->getRepository(UserGroup::class)->findOneBy(['name' => self::USER_GROUP_DELETE]) + ); + } +} \ No newline at end of file diff --git a/tests/Functional/UserTest.php b/tests/Functional/UserTest.php index 8b83430..b0e9cc4 100644 --- a/tests/Functional/UserTest.php +++ b/tests/Functional/UserTest.php @@ -14,9 +14,9 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; class UserTest extends AbstractTest { CONST USER_ADMIN = 'ogadmin'; - CONST USER_CREATE = 'test-create'; - CONST USER_UPDATE = 'test-update'; - CONST USER_DELETE = 'test-delete'; + CONST USER_CREATE = 'test-user-create'; + CONST USER_UPDATE = 'test-user-update'; + CONST USER_DELETE = 'test-user-delete'; /** * @throws RedirectionExceptionInterface @@ -25,7 +25,7 @@ class UserTest extends AbstractTest * @throws TransportExceptionInterface * @throws ServerExceptionInterface */ - public function testGetCollection(): void + public function testGetCollectionUser(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); UserFactory::createMany(10); @@ -48,7 +48,7 @@ class UserTest extends AbstractTest * @throws TransportExceptionInterface * @throws ServerExceptionInterface */ - public function testCreate(): void + public function testCreateUser(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); $this->createClientWithCredentials()->request('POST', '/api/users',['json' => [ @@ -74,7 +74,7 @@ class UserTest extends AbstractTest * @throws TransportExceptionInterface * @throws ServerExceptionInterface */ - public function testUpdateBook(): void + public function testUpdateUser(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); UserFactory::createOne(['username' => self::USER_UPDATE]); @@ -99,7 +99,7 @@ class UserTest extends AbstractTest * @throws DecodingExceptionInterface * @throws ClientExceptionInterface */ - public function testDeleteBook(): void + public function testDeleteUser(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); UserFactory::createOne(['username' => self::USER_DELETE]); From 7fdda457ee586af5662d000a84de1c4c15921647 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Mon, 27 May 2024 16:30:22 +0200 Subject: [PATCH 08/15] refs #378. Funcionality link organizationalUnit to user --- config/api_platform/OrganizationalUnit.yaml | 0 src/Dto/Input/OrganizationalUnitInput.php | 8 ++++++++ src/Dto/Output/OrganizationalUnitOutput.php | 8 ++++++++ 3 files changed, 16 insertions(+) create mode 100644 config/api_platform/OrganizationalUnit.yaml create mode 100644 src/Dto/Input/OrganizationalUnitInput.php create mode 100644 src/Dto/Output/OrganizationalUnitOutput.php diff --git a/config/api_platform/OrganizationalUnit.yaml b/config/api_platform/OrganizationalUnit.yaml new file mode 100644 index 0000000..e69de29 diff --git a/src/Dto/Input/OrganizationalUnitInput.php b/src/Dto/Input/OrganizationalUnitInput.php new file mode 100644 index 0000000..b2eb2ed --- /dev/null +++ b/src/Dto/Input/OrganizationalUnitInput.php @@ -0,0 +1,8 @@ + Date: Tue, 28 May 2024 14:19:01 +0200 Subject: [PATCH 09/15] refs #378. Funcionality link organizationalUnit to user. Create organizationalUnit API and migration --- README.md | 4 +- config/api_platform/OrganizationalUnit.yaml | 27 + config/api_platform/User.yaml | 2 +- config/packages/api_platform.yaml | 2 +- config/packages/stof_doctrine_extensions.yaml | 4 +- config/services.yaml | 4 + migrations/Version20240517085550.php | 31 - migrations/Version20240517101651.php | 31 - migrations/Version20240523091838.php | 33 - migrations/Version20240523093000.php | 31 - migrations/Version20240523093036.php | 31 - migrations/Version20240523094836.php | 37 - migrations/Version20240524063952.php | 33 - migrations/Version20240524065625.php | 38 - migrations/Version20240524083309.php | 35 - migrations/Version20240527103936.php | 33 - migrations/Version20240528093352.php | 67 ++ .../MigrateOrganizationalUnitCommand.php | 152 ++++ src/DataFixtures/AppFixtures.php | 19 - src/Dto/Input/OrganizationalUnitInput.php | 30 +- src/Dto/Input/UserInput.php | 5 +- src/Dto/Output/AbstractOutput.php | 9 +- src/Dto/Output/OrganizationalUnitOutput.php | 25 +- src/Entity/Client.php | 131 +++ src/Entity/Migration/Aulas.php | 753 ++++++++++++++++++ src/Entity/Migration/Centros.php | 133 ++++ src/Entity/Migration/Gruposordenadores.php | 133 ++++ src/Entity/Migration/Ordenadores.php | 663 +++++++++++++++ src/Entity/NameableTrait.php | 2 + src/Entity/OrganizationalUnit.php | 79 +- src/Entity/ToggleableTrait.php | 2 + src/Entity/User.php | 13 +- src/Entity/UserGroup.php | 16 +- src/Repository/ClientRepository.php | 43 + .../OrganizationalUnitRepository.php | 27 +- .../Processor/OrganizationalUnitProcessor.php | 68 ++ src/State/Processor/UserGroupProcessor.php | 1 - .../Provider/OrganizationalUnitProvider.php | 71 ++ 38 files changed, 2405 insertions(+), 413 deletions(-) delete mode 100644 migrations/Version20240517085550.php delete mode 100644 migrations/Version20240517101651.php delete mode 100644 migrations/Version20240523091838.php delete mode 100644 migrations/Version20240523093000.php delete mode 100644 migrations/Version20240523093036.php delete mode 100644 migrations/Version20240523094836.php delete mode 100644 migrations/Version20240524063952.php delete mode 100644 migrations/Version20240524065625.php delete mode 100644 migrations/Version20240524083309.php delete mode 100644 migrations/Version20240527103936.php create mode 100644 migrations/Version20240528093352.php create mode 100644 src/Command/Migration/MigrateOrganizationalUnitCommand.php create mode 100644 src/Entity/Client.php create mode 100644 src/Entity/Migration/Aulas.php create mode 100644 src/Entity/Migration/Centros.php create mode 100644 src/Entity/Migration/Gruposordenadores.php create mode 100644 src/Entity/Migration/Ordenadores.php create mode 100644 src/Repository/ClientRepository.php create mode 100644 src/State/Processor/OrganizationalUnitProcessor.php create mode 100644 src/State/Provider/OrganizationalUnitProvider.php diff --git a/README.md b/README.md index 385f4db..37c0e4f 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,13 @@ Para poder actualizar la base de datos: Para inicializar la base de datos: ```sh -docker exec ogcore-php php bin/consoledoctrine:migrations:migrate --no-interaction +docker exec ogcore-php php bin/console doctrine:migrations:migrate --no-interaction ``` ```sh docker exec ogcore-php php bin/console doctrine:fixtures:load --no-interaction +docker exec ogcore-php php bin/console app:load-default-user-groups + ``` ## Test diff --git a/config/api_platform/OrganizationalUnit.yaml b/config/api_platform/OrganizationalUnit.yaml index e69de29..577b80a 100644 --- a/config/api_platform/OrganizationalUnit.yaml +++ b/config/api_platform/OrganizationalUnit.yaml @@ -0,0 +1,27 @@ +resources: + App\Entity\OrganizationalUnit: + processor: App\State\Processor\OrganizationalUnitProcessor + input: App\Dto\Input\OrganizationalUnitInput + output: App\Dto\Output\OrganizationalUnitOutput + normalization_context: + groups: ['default', 'organizational-unit:read'] + denormalization_context: + groups: ['organizational-unit:write'] + operations: + ApiPlatform\Metadata\GetCollection: + provider: App\State\Provider\OrganizationalUnitProvider + ApiPlatform\Metadata\Get: + provider: App\State\Provider\OrganizationalUnitProvider + ApiPlatform\Metadata\Put: + provider: App\State\Provider\OrganizationalUnitProvider + ApiPlatform\Metadata\Patch: + provider: App\State\Provider\OrganizationalUnitProvider + ApiPlatform\Metadata\Post: ~ + ApiPlatform\Metadata\Delete: ~ + +properties: + App\Entity\OrganizationalUnit: + id: + identifier: false + uuid: + identifier: true \ No newline at end of file diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index 8a75827..17e4801 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -1,8 +1,8 @@ resources: App\Entity\User: - processor: App\State\Processor\UserProcessor input: App\Dto\Input\UserInput output: App\Dto\Output\UserOutput + processor: App\State\Processor\UserProcessor normalization_context: groups: ['default', 'user:read'] denormalization_context: diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 160cfd2..8fad293 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -17,7 +17,7 @@ api_platform: standard_put: true rfc_7807_compliant_errors: true event_listeners_backward_compatibility_layer: false - keep_legacy_inflector: false + keep_legacy_inflector: true docs_formats: jsonld: ['application/ld+json'] jsonopenapi: ['application/vnd.openapi+json'] diff --git a/config/packages/stof_doctrine_extensions.yaml b/config/packages/stof_doctrine_extensions.yaml index 1432072..c478c2c 100644 --- a/config/packages/stof_doctrine_extensions.yaml +++ b/config/packages/stof_doctrine_extensions.yaml @@ -3,4 +3,6 @@ stof_doctrine_extensions: orm: default: timestampable: true - blameable: true \ No newline at end of file + blameable: true + tree: true + sluggable: true \ No newline at end of file diff --git a/config/services.yaml b/config/services.yaml index af7a6a0..7e12544 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -35,3 +35,7 @@ services: $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' $itemProvider: '@api_platform.doctrine.orm.state.item_provider' + App\State\Provider\OrganizationalUnitProvider: + bind: + $collectionProvider: '@api_platform.doctrine.orm.state.collection_provider' + $itemProvider: '@api_platform.doctrine.orm.state.item_provider' \ No newline at end of file diff --git a/migrations/Version20240517085550.php b/migrations/Version20240517085550.php deleted file mode 100644 index bec091e..0000000 --- a/migrations/Version20240517085550.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_IDENTIFIER_USERNAME (username), 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 user'); - } -} diff --git a/migrations/Version20240517101651.php b/migrations/Version20240517101651.php deleted file mode 100644 index 66bf841..0000000 --- a/migrations/Version20240517101651.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('CREATE TABLE refresh_tokens (id INT AUTO_INCREMENT NOT NULL, refresh_token VARCHAR(128) NOT NULL, username VARCHAR(255) NOT NULL, valid DATETIME NOT NULL, UNIQUE INDEX UNIQ_9BACE7E1C74F2195 (refresh_token), 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 refresh_tokens'); - } -} diff --git a/migrations/Version20240523091838.php b/migrations/Version20240523091838.php deleted file mode 100644 index 6f955c4..0000000 --- a/migrations/Version20240523091838.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('ALTER TABLE user ADD uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', ADD migration_id VARCHAR(255) DEFAULT NULL'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649D17F50A6 ON user (uuid)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP INDEX UNIQ_8D93D649D17F50A6 ON user'); - $this->addSql('ALTER TABLE user DROP uuid, DROP migration_id'); - } -} diff --git a/migrations/Version20240523093000.php b/migrations/Version20240523093000.php deleted file mode 100644 index 16f1851..0000000 --- a/migrations/Version20240523093000.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user DROP created_at, DROP updated_at'); - } -} diff --git a/migrations/Version20240523093036.php b/migrations/Version20240523093036.php deleted file mode 100644 index 5d736be..0000000 --- a/migrations/Version20240523093036.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE user ADD created_at DATETIME NOT NULL, ADD updated_at DATETIME NOT NULL, ADD created_by VARCHAR(255) DEFAULT NULL, ADD updated_by VARCHAR(255) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user DROP created_at, DROP updated_at, DROP created_by, DROP updated_by'); - } -} diff --git a/migrations/Version20240523094836.php b/migrations/Version20240523094836.php deleted file mode 100644 index 478930d..0000000 --- a/migrations/Version20240523094836.php +++ /dev/null @@ -1,37 +0,0 @@ -addSql('CREATE TABLE user_group (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, name VARCHAR(255) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', UNIQUE INDEX UNIQ_8F02BF9DD17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('CREATE TABLE user_group_user (user_group_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_3AE4BD51ED93D47 (user_group_id), INDEX IDX_3AE4BD5A76ED395 (user_id), PRIMARY KEY(user_group_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD51ED93D47 FOREIGN KEY (user_group_id) REFERENCES user_group (id) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD5A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD51ED93D47'); - $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD5A76ED395'); - $this->addSql('DROP TABLE user_group'); - $this->addSql('DROP TABLE user_group_user'); - } -} diff --git a/migrations/Version20240524063952.php b/migrations/Version20240524063952.php deleted file mode 100644 index f3f4231..0000000 --- a/migrations/Version20240524063952.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('CREATE TABLE organizational_unit (id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT 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, description VARCHAR(255) DEFAULT NULL, comments VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_749AEB2DD17F50A6 (uuid), INDEX IDX_749AEB2D727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D727ACA70 FOREIGN KEY (parent_id) REFERENCES organizational_unit (id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D727ACA70'); - $this->addSql('DROP TABLE organizational_unit'); - } -} diff --git a/migrations/Version20240524065625.php b/migrations/Version20240524065625.php deleted file mode 100644 index 6941a70..0000000 --- a/migrations/Version20240524065625.php +++ /dev/null @@ -1,38 +0,0 @@ -addSql('CREATE TABLE network_settings (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, proxy VARCHAR(255) DEFAULT NULL, dns VARCHAR(255) DEFAULT NULL, netmask VARCHAR(255) DEFAULT NULL, router VARCHAR(255) DEFAULT NULL, ntp VARCHAR(255) DEFAULT NULL, p2p_time INT DEFAULT NULL, p2p_mode VARCHAR(255) DEFAULT NULL, mcast_ip VARCHAR(255) DEFAULT NULL, mcast_speed INT NOT NULL, mcast_mode VARCHAR(255) DEFAULT NULL, mcast_port INT DEFAULT NULL, UNIQUE INDEX UNIQ_48869B54D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - - $this->addSql('ALTER TABLE organizational_unit ADD network_settings_id INT DEFAULT NULL, ADD path VARCHAR(255) DEFAULT NULL'); - $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D9B9A36D0 FOREIGN KEY (network_settings_id) REFERENCES network_settings (id)'); - $this->addSql('CREATE INDEX IDX_749AEB2D9B9A36D0 ON organizational_unit (network_settings_id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D9B9A36D0'); - $this->addSql('DROP TABLE network_settings'); - $this->addSql('DROP INDEX IDX_749AEB2D9B9A36D0 ON organizational_unit'); - $this->addSql('ALTER TABLE organizational_unit DROP network_settings_id, DROP path'); - } -} diff --git a/migrations/Version20240524083309.php b/migrations/Version20240524083309.php deleted file mode 100644 index cee2d72..0000000 --- a/migrations/Version20240524083309.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE user_organizational_unit (user_id INT NOT NULL, organizational_unit_id INT NOT NULL, INDEX IDX_5E59845FA76ED395 (user_id), INDEX IDX_5E59845FFB84408A (organizational_unit_id), PRIMARY KEY(user_id, organizational_unit_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); - $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); - $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FFB84408A FOREIGN KEY (organizational_unit_id) REFERENCES organizational_unit (id) ON DELETE CASCADE'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FA76ED395'); - $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FFB84408A'); - $this->addSql('DROP TABLE user_organizational_unit'); - } -} diff --git a/migrations/Version20240527103936.php b/migrations/Version20240527103936.php deleted file mode 100644 index 802e375..0000000 --- a/migrations/Version20240527103936.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('ALTER TABLE user_group CHANGE roles permissions JSON NOT NULL COMMENT \'(DC2Type:json)\''); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_NAME ON user_group (name)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP INDEX UNIQ_IDENTIFIER_NAME ON user_group'); - $this->addSql('ALTER TABLE user_group CHANGE permissions roles JSON NOT NULL COMMENT \'(DC2Type:json)\''); - } -} diff --git a/migrations/Version20240528093352.php b/migrations/Version20240528093352.php new file mode 100644 index 0000000..60ec26a --- /dev/null +++ b/migrations/Version20240528093352.php @@ -0,0 +1,67 @@ +addSql('CREATE TABLE aulas (idaula INT AUTO_INCREMENT NOT NULL, nombreaula VARCHAR(255) NOT NULL, idcentro INT NOT NULL, urlfoto VARCHAR(255) NOT NULL, cagnon TINYINT(1) NOT NULL, pizarra TINYINT(1) NOT NULL, grupoid INT NOT NULL, ubicacion VARCHAR(255) NOT NULL, comentarios VARCHAR(255) NOT NULL, puestos INT NOT NULL, horaresevini TINYINT(1) NOT NULL, horaresevfin TINYINT(1) NOT NULL, modomul TINYINT(1) NOT NULL, ipmul VARCHAR(255) NOT NULL, pormul VARCHAR(255) NOT NULL, velmul VARCHAR(255) NOT NULL, router VARCHAR(255) NOT NULL, netmask VARCHAR(255) NOT NULL, dns VARCHAR(255) NOT NULL, proxy VARCHAR(255) NOT NULL, ntp VARCHAR(255) NOT NULL, modp2p VARCHAR(255) NOT NULL, timep2p VARCHAR(255) NOT NULL, validacion TINYINT(1) NOT NULL, paginalogin VARCHAR(255) NOT NULL, paginavalidacion VARCHAR(255) NOT NULL, inremotepc VARCHAR(255) NOT NULL, oglivedir VARCHAR(255) NOT NULL, PRIMARY KEY(idaula)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE centros (idcentro INT AUTO_INCREMENT NOT NULL, nombrecentro VARCHAR(255) NOT NULL, identidad INT NOT NULL, comentarios VARCHAR(255) DEFAULT NULL, directorio VARCHAR(255) DEFAULT NULL, PRIMARY KEY(idcentro)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE client (id INT AUTO_INCREMENT NOT NULL, organizational_unit_id INT DEFAULT 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, name VARCHAR(255) DEFAULT NULL, serial_number VARCHAR(255) DEFAULT NULL, netiface VARCHAR(255) DEFAULT NULL, net_driver VARCHAR(255) DEFAULT NULL, mac VARCHAR(255) DEFAULT NULL, ip VARCHAR(255) DEFAULT NULL, status VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_C7440455D17F50A6 (uuid), INDEX IDX_C7440455FB84408A (organizational_unit_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE gruposordenadores (idgrupo INT AUTO_INCREMENT NOT NULL, nombregrupoordenador VARCHAR(255) NOT NULL, idaula VARCHAR(255) NOT NULL, grupoid VARCHAR(255) NOT NULL, comentarios VARCHAR(255) NOT NULL, PRIMARY KEY(idgrupo)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE network_settings (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, proxy VARCHAR(255) DEFAULT NULL, dns VARCHAR(255) DEFAULT NULL, netmask VARCHAR(255) DEFAULT NULL, router VARCHAR(255) DEFAULT NULL, ntp VARCHAR(255) DEFAULT NULL, p2p_time INT DEFAULT NULL, p2p_mode VARCHAR(255) DEFAULT NULL, mcast_ip VARCHAR(255) DEFAULT NULL, mcast_speed INT NOT NULL, mcast_mode VARCHAR(255) DEFAULT NULL, mcast_port INT DEFAULT NULL, UNIQUE INDEX UNIQ_48869B54D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE ordenadores (idordenador INT AUTO_INCREMENT NOT NULL, PRIMARY KEY(idordenador)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE organizational_unit (id INT AUTO_INCREMENT NOT NULL, parent_id INT DEFAULT NULL, network_settings_id INT DEFAULT 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, description VARCHAR(255) DEFAULT NULL, comments VARCHAR(255) DEFAULT NULL, path VARCHAR(255) DEFAULT NULL, level INT DEFAULT NULL, slug VARCHAR(255) DEFAULT NULL, name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_749AEB2DD17F50A6 (uuid), INDEX IDX_749AEB2D727ACA70 (parent_id), INDEX IDX_749AEB2D9B9A36D0 (network_settings_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE refresh_tokens (id INT AUTO_INCREMENT NOT NULL, refresh_token VARCHAR(128) NOT NULL, username VARCHAR(255) NOT NULL, valid DATETIME NOT NULL, UNIQUE INDEX UNIQ_9BACE7E1C74F2195 (refresh_token), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user (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, username VARCHAR(180) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_8D93D649D17F50A6 (uuid), UNIQUE INDEX UNIQ_IDENTIFIER_USERNAME (username), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user_organizational_unit (user_id INT NOT NULL, organizational_unit_id INT NOT NULL, INDEX IDX_5E59845FA76ED395 (user_id), INDEX IDX_5E59845FFB84408A (organizational_unit_id), PRIMARY KEY(user_id, organizational_unit_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user_group (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, permissions JSON NOT NULL COMMENT \'(DC2Type:json)\', name VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_8F02BF9DD17F50A6 (uuid), UNIQUE INDEX UNIQ_IDENTIFIER_NAME (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('CREATE TABLE user_group_user (user_group_id INT NOT NULL, user_id INT NOT NULL, INDEX IDX_3AE4BD51ED93D47 (user_group_id), INDEX IDX_3AE4BD5A76ED395 (user_id), PRIMARY KEY(user_group_id, user_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE client ADD CONSTRAINT FK_C7440455FB84408A FOREIGN KEY (organizational_unit_id) REFERENCES organizational_unit (id)'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D727ACA70 FOREIGN KEY (parent_id) REFERENCES organizational_unit (id)'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D9B9A36D0 FOREIGN KEY (network_settings_id) REFERENCES network_settings (id)'); + $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FA76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE user_organizational_unit ADD CONSTRAINT FK_5E59845FFB84408A FOREIGN KEY (organizational_unit_id) REFERENCES organizational_unit (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD51ED93D47 FOREIGN KEY (user_group_id) REFERENCES user_group (id) ON DELETE CASCADE'); + $this->addSql('ALTER TABLE user_group_user ADD CONSTRAINT FK_3AE4BD5A76ED395 FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE client DROP FOREIGN KEY FK_C7440455FB84408A'); + $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D727ACA70'); + $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D9B9A36D0'); + $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FA76ED395'); + $this->addSql('ALTER TABLE user_organizational_unit DROP FOREIGN KEY FK_5E59845FFB84408A'); + $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD51ED93D47'); + $this->addSql('ALTER TABLE user_group_user DROP FOREIGN KEY FK_3AE4BD5A76ED395'); + $this->addSql('DROP TABLE aulas'); + $this->addSql('DROP TABLE centros'); + $this->addSql('DROP TABLE client'); + $this->addSql('DROP TABLE gruposordenadores'); + $this->addSql('DROP TABLE network_settings'); + $this->addSql('DROP TABLE ordenadores'); + $this->addSql('DROP TABLE organizational_unit'); + $this->addSql('DROP TABLE refresh_tokens'); + $this->addSql('DROP TABLE user'); + $this->addSql('DROP TABLE user_organizational_unit'); + $this->addSql('DROP TABLE user_group'); + $this->addSql('DROP TABLE user_group_user'); + } +} diff --git a/src/Command/Migration/MigrateOrganizationalUnitCommand.php b/src/Command/Migration/MigrateOrganizationalUnitCommand.php new file mode 100644 index 0000000..a595626 --- /dev/null +++ b/src/Command/Migration/MigrateOrganizationalUnitCommand.php @@ -0,0 +1,152 @@ +entityManagerSlave->getRepository(Centros::class)->findAll(); + $rooms = $this->entityManagerSlave->getRepository(Aulas::class)->findAll(); + $pcGroups = $this->entityManagerSlave->getRepository(Gruposordenadores::class)->findAll(); + $pcs = $this->entityManagerSlave->getRepository(Ordenadores::class)->findAll(); + + $organizationalUnitRepository = $this->entityManager->getRepository(OrganizationalUnit::class); + $clientRepository = $this->entityManager->getRepository(Client::class); + + /** Centros **/ + $output->writeln("CENTROS TOTAL: ". count($centers)); + foreach ($centers as $center){ + $centerOrganizationalUnit = null; + $centerOrganizationalUnit = $organizationalUnitRepository->findOneBy(['migrationId' => $center->getIdcentro()]); + if(!$centerOrganizationalUnit){ + $centerOrganizationalUnit = new OrganizationalUnit(); + $centerOrganizationalUnit->setMigrationId($center->getIdcentro()); + $centerOrganizationalUnit->setName($center->getNombrecentro()); + $centerOrganizationalUnit->setComments($center->getComentarios()); + $this->entityManager->persist($centerOrganizationalUnit); + } + } + $this->entityManager->flush(); + + /** Aulas **/ + $output->writeln("AULAS TOTAL: ". count($rooms)); + foreach ($rooms as $room){ + $roomOrganizationalUnit = null; + $roomOrganizationalUnit = $organizationalUnitRepository->findOneBy(['migrationId' => $room->getIdaula()]); + if(!$roomOrganizationalUnit){ + $roomOrganizationalUnit = new OrganizationalUnit(); + $roomOrganizationalUnit->setMigrationId($room->getIdaula()); + $roomOrganizationalUnit->setName($room->getNombreaula()); + $roomOrganizationalUnitParent = $organizationalUnitRepository->findOneBy(['migrationId' => $room->getIdcentro()]); + $roomOrganizationalUnit->setParent($roomOrganizationalUnitParent); + $this->entityManager->persist($roomOrganizationalUnit); + } + + $networkSettings = $roomOrganizationalUnit->getNetworkSettings(); + if(!$networkSettings){ + $networkSettings = new NetworkSettings(); + $roomOrganizationalUnit->setNetworkSettings($networkSettings); + $this->entityManager->persist($networkSettings); + } + $networkSettings->setProxy($room->getProxy()); + $networkSettings->setDns($room->getDns()); + $networkSettings->setNetmask($room->getNetmask()); + $networkSettings->setRouter($room->getRouter()); + $networkSettings->setNtp($room->getNtp()); + $networkSettings->setP2pTime($room->getTimep2p()); + $networkSettings->setP2pMode($room->getModp2p()); + $networkSettings->setMcastIp($room->getIpmul()); + $networkSettings->setMcastSpeed($room->getVelmul()); + $networkSettings->setMcastPort($room->getPormul()); + $networkSettings->setMcastMode($room->getModomul()); + } + + $this->entityManager->flush(); + + /** Grupo Ordenador **/ + $output->writeln("GRUPOS ORDENADORES ". count($rooms)); + foreach ($pcGroups as $group){ + $groupPcOrganizationalUnit = null; + $migrateParentId = ($group->getGrupoid() == 0) ? $group->getIdaula() : $group->getGrupoid(); + $groupPcOrganizationalUnit = $organizationalUnitRepository->findOneBy(['migrationId' => $group->getIdgrupo()]); + if(!$groupPcOrganizationalUnit){ + $groupPcOrganizationalUnit = new OrganizationalUnit(); + $groupPcOrganizationalUnit->setMigrationId($group->getIdgrupo()); + $groupPcOrganizationalUnit->setName($group->getNombregrupoordenador()); + $groupPcOrganizationalUnit->setComments($group->getComentarios()); + $groupPcOrganizationalUnitParent = $organizationalUnitRepository->findOneBy(['migrationId' => $migrateParentId]); + $groupPcOrganizationalUnit->setParent($groupPcOrganizationalUnitParent); + $this->entityManager->persist($groupPcOrganizationalUnit); + } + } + $this->entityManager->flush(); + + /** Ordenadores **/ + foreach ($pcs as $pc){ + $newClient = $clientRepository->findOneBy(['migrationId' => $pc->getIdordenador()]); + if(!$newClient){ + $newClient = new Client(); + $newClient->setMigrationId($pc->getIdordenador()); + $this->entityManager->persist($newClient); + } + $newClient->setName($pc->getNombreordenador()); + $newClient->setSerialNumber($pc->getNumserie()); + $newClient->setNetiface($pc->getNetiface()); + $newClient->setNetdriver($pc->getNetdriver()); + $newClient->setMac($pc->getMac()); + $newClient->setIp($pc->getIp()); + //$client->setStatus(); + //$newClient->setCache($pc->getCache()); + //$newClient->setIdproautoexec($pc->getIdproautoexec()); + //$newClient->setOglive($pc->getOglivedir()); + + // Netboot + + //$migrationId = "" + + // HardwareProfile + //$hardwareProfile = $hardwareProfileRepository->findOneBy(['migrationId' => $pc->getIdperfilhard()]); + //$newClient->setHardwareProfile($hardwareProfile); + + // Menu + //$menu = $menuRepository->findOneBy(['migrationId' => $pc->getIdmenu()]); + //$newClient->setMenu($menu); + + // Repository + //$repository = $repositoryRepository->findOneBy(['migrationId' => $pc->getIdrepositorio()]); + //$newClient->setRepository($repository); + + // OrganizationalUnit + $migrationId = $pc->getGrupoid() == 0 ? $pc->getIdaula() : $pc->getGrupoid(); + $organizationalUnit = $organizationalUnitRepository->findOneBy(['migrationId' => $migrationId]); + $newClient->setOrganizationalUnit($organizationalUnit); + + } + $this->entityManager->flush(); + + return 1; + } +} \ No newline at end of file diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index ded5655..8feb58e 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -5,36 +5,17 @@ namespace App\DataFixtures; use App\Factory\UserFactory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; -use Symfony\Bundle\FrameworkBundle\Console\Application; -use Symfony\Component\Console\Input\ArrayInput; -use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\HttpKernel\KernelInterface; class AppFixtures extends Fixture { CONST ADMIN_USER = 'ogadmin'; - public function __construct( - private readonly KernelInterface $kernel, - ) - { - } - /** * @throws \Exception */ public function load(ObjectManager $manager): void { UserFactory::createOne(['username' => self::ADMIN_USER]); - - $application = new Application($this->kernel); - - $input = new ArrayInput([ - 'command' => 'app:load-default-user-groups' - ]); - - $output = new BufferedOutput(); - $application->run($input, $output); } } diff --git a/src/Dto/Input/OrganizationalUnitInput.php b/src/Dto/Input/OrganizationalUnitInput.php index b2eb2ed..53f8d40 100644 --- a/src/Dto/Input/OrganizationalUnitInput.php +++ b/src/Dto/Input/OrganizationalUnitInput.php @@ -2,7 +2,33 @@ namespace App\Dto\Input; -class OrganizationalUnitInput -{ +use App\Entity\OrganizationalUnit; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Validator\Constraints as Assert; +final class OrganizationalUnitInput +{ + #[Assert\NotBlank] + #[Groups(['organizational-unit:write'])] + public ?string $name = null; + + public function __construct(?OrganizationalUnit $organizationalUnit = null) + { + if (!$organizationalUnit) { + return; + } + + $this->name = $organizationalUnit->getName(); + } + + public function createOrUpdateEntity(?OrganizationalUnit $organizationalUnit = null): OrganizationalUnit + { + if (!$organizationalUnit) { + $organizationalUnit = new OrganizationalUnit(); + } + + $organizationalUnit->setName($this->name); + + return $organizationalUnit; + } } \ No newline at end of file diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index ce9e9a4..379c5f9 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -70,7 +70,10 @@ final class UserInput $user->setPassword($this->password); } - //$user->setAllowedOrganizationalUnits($this->allowedOrganizationalUnits); + foreach ($this->allowedOrganizationalUnits as $allowedOrganizationalUnit) { + $allowedOrganizationalUnitToAdd[] = $allowedOrganizationalUnit->getEntity(); + } + $user->setAllowedOrganizationalUnits( $allowedOrganizationalUnitToAdd ?? [] ); return $user; } diff --git a/src/Dto/Output/AbstractOutput.php b/src/Dto/Output/AbstractOutput.php index 2ca73e0..93f7e50 100644 --- a/src/Dto/Output/AbstractOutput.php +++ b/src/Dto/Output/AbstractOutput.php @@ -10,20 +10,21 @@ use Symfony\Component\Serializer\Annotation\Groups; abstract class AbstractOutput { #[ApiProperty(identifier: true)] - #[Groups(['default'])] + #[Groups('default')] public UuidInterface $uuid; #[ApiProperty(identifier: false)] - #[Groups(['default'])] + #[Groups('default')] public int $id; public function __construct(private readonly AbstractEntity $entity) { - $this->uuid = $entity->getUuid(); $this->id = $entity->getId(); + $this->uuid = $entity->getUuid(); } - public function getEntity(): AbstractEntity + #[ApiProperty(readable: false)] + public function getEntity(): ?AbstractEntity { return $this->entity; } diff --git a/src/Dto/Output/OrganizationalUnitOutput.php b/src/Dto/Output/OrganizationalUnitOutput.php index 204ee06..1b8bf80 100644 --- a/src/Dto/Output/OrganizationalUnitOutput.php +++ b/src/Dto/Output/OrganizationalUnitOutput.php @@ -2,7 +2,28 @@ namespace App\Dto\Output; -class OrganizationalUnitOutput -{ +use ApiPlatform\Metadata\Get; +use App\Entity\OrganizationalUnit; +use Symfony\Component\Serializer\Annotation\Groups; +#[Get(shortName: 'OrganizationalUnit')] +final class OrganizationalUnitOutput extends AbstractOutput +{ + #[Groups(['organizational-unit:read'])] + public string $name; + + #[Groups(['organizational-unit:read'])] + public \DateTime $createAt; + + #[Groups(['organizational-unit:read'])] + public ?string $createBy = null; + + public function __construct(OrganizationalUnit $organizationalUnit) + { + parent::__construct($organizationalUnit); + + $this->name = $organizationalUnit->getName(); + $this->createAt = $organizationalUnit->getCreatedAt(); + $this->createBy = $organizationalUnit->getCreatedBy(); + } } \ No newline at end of file diff --git a/src/Entity/Client.php b/src/Entity/Client.php new file mode 100644 index 0000000..4fa39ea --- /dev/null +++ b/src/Entity/Client.php @@ -0,0 +1,131 @@ +name; + } + + public function setName(?string $name): static + { + $this->name = $name; + + return $this; + } + + public function getSerialNumber(): ?string + { + return $this->serialNumber; + } + + public function setSerialNumber(?string $serialNumber): static + { + $this->serialNumber = $serialNumber; + + return $this; + } + + public function getNetiface(): ?string + { + return $this->netiface; + } + + public function setNetiface(?string $netiface): static + { + $this->netiface = $netiface; + + return $this; + } + + public function getNetDriver(): ?string + { + return $this->netDriver; + } + + public function setNetDriver(string $netDriver): static + { + $this->netDriver = $netDriver; + + return $this; + } + + public function getMac(): ?string + { + return $this->mac; + } + + public function setMac(?string $mac): static + { + $this->mac = $mac; + + return $this; + } + + public function getIp(): ?string + { + return $this->ip; + } + + public function setIp(?string $ip): static + { + $this->ip = $ip; + + return $this; + } + + public function getStatus(): ?string + { + return $this->status; + } + + public function setStatus(?string $status): static + { + $this->status = $status; + + return $this; + } + + public function getOrganizationalUnit(): ?OrganizationalUnit + { + return $this->organizationalUnit; + } + + public function setOrganizationalUnit(?OrganizationalUnit $organizationalUnit): static + { + $this->organizationalUnit = $organizationalUnit; + + return $this; + } +} diff --git a/src/Entity/Migration/Aulas.php b/src/Entity/Migration/Aulas.php new file mode 100644 index 0000000..d4179e3 --- /dev/null +++ b/src/Entity/Migration/Aulas.php @@ -0,0 +1,753 @@ +nombreaula = $nombreaula; + + return $this; + } + + /** + * Get nombreaula. + * + * @return string + */ + public function getNombreaula() + { + return $this->nombreaula; + } + + /** + * Set idcentro. + * + * @param int $idcentro + * + * @return Aulas + */ + public function setIdcentro($idcentro) + { + $this->idcentro = $idcentro; + + return $this; + } + + /** + * Get idcentro. + * + * @return int + */ + public function getIdcentro() + { + return $this->idcentro; + } + + /** + * Set urlfoto. + * + * @param string|null $urlfoto + * + * @return Aulas + */ + public function setUrlfoto($urlfoto = null) + { + $this->urlfoto = $urlfoto; + + return $this; + } + + /** + * Get urlfoto. + * + * @return string|null + */ + public function getUrlfoto() + { + return $this->urlfoto; + } + + /** + * Set cagnon. + * + * @param bool|null $cagnon + * + * @return Aulas + */ + public function setCagnon($cagnon = null) + { + $this->cagnon = $cagnon; + + return $this; + } + + /** + * Get cagnon. + * + * @return bool|null + */ + public function getCagnon() + { + return $this->cagnon; + } + + /** + * Set pizarra. + * + * @param bool|null $pizarra + * + * @return Aulas + */ + public function setPizarra($pizarra = null) + { + $this->pizarra = $pizarra; + + return $this; + } + + /** + * Get pizarra. + * + * @return bool|null + */ + public function getPizarra() + { + return $this->pizarra; + } + + /** + * Set grupoid. + * + * @param int|null $grupoid + * + * @return Aulas + */ + public function setGrupoid($grupoid = null) + { + $this->grupoid = $grupoid; + + return $this; + } + + /** + * Get grupoid. + * + * @return int|null + */ + public function getGrupoid() + { + return $this->grupoid; + } + + /** + * Set ubicacion. + * + * @param string|null $ubicacion + * + * @return Aulas + */ + public function setUbicacion($ubicacion = null) + { + $this->ubicacion = $ubicacion; + + return $this; + } + + /** + * Get ubicacion. + * + * @return string|null + */ + public function getUbicacion() + { + return $this->ubicacion; + } + + /** + * Set comentarios. + * + * @param string|null $comentarios + * + * @return Aulas + */ + public function setComentarios($comentarios = null) + { + $this->comentarios = $comentarios; + + return $this; + } + + /** + * Get comentarios. + * + * @return string|null + */ + public function getComentarios() + { + return $this->comentarios; + } + + /** + * Set puestos. + * + * @param int|null $puestos + * + * @return Aulas + */ + public function setPuestos($puestos = null) + { + $this->puestos = $puestos; + + return $this; + } + + /** + * Get puestos. + * + * @return int|null + */ + public function getPuestos() + { + return $this->puestos; + } + + /** + * Set horaresevini. + * + * @param bool|null $horaresevini + * + * @return Aulas + */ + public function setHoraresevini($horaresevini = null) + { + $this->horaresevini = $horaresevini; + + return $this; + } + + /** + * Get horaresevini. + * + * @return bool|null + */ + public function getHoraresevini() + { + return $this->horaresevini; + } + + /** + * Set horaresevfin. + * + * @param bool|null $horaresevfin + * + * @return Aulas + */ + public function setHoraresevfin($horaresevfin = null) + { + $this->horaresevfin = $horaresevfin; + + return $this; + } + + /** + * Get horaresevfin. + * + * @return bool|null + */ + public function getHoraresevfin() + { + return $this->horaresevfin; + } + + /** + * Set modomul. + * + * @param bool $modomul + * + * @return Aulas + */ + public function setModomul($modomul) + { + $this->modomul = $modomul; + + return $this; + } + + /** + * Get modomul. + * + * @return bool + */ + public function getModomul() + { + return $this->modomul; + } + + /** + * Set ipmul. + * + * @param string $ipmul + * + * @return Aulas + */ + public function setIpmul($ipmul) + { + $this->ipmul = $ipmul; + + return $this; + } + + /** + * Get ipmul. + * + * @return string + */ + public function getIpmul() + { + return $this->ipmul; + } + + /** + * Set pormul. + * + * @param int $pormul + * + * @return Aulas + */ + public function setPormul($pormul) + { + $this->pormul = $pormul; + + return $this; + } + + /** + * Get pormul. + * + * @return int + */ + public function getPormul() + { + return $this->pormul; + } + + /** + * Set velmul. + * + * @param int $velmul + * + * @return Aulas + */ + public function setVelmul($velmul) + { + $this->velmul = $velmul; + + return $this; + } + + /** + * Get velmul. + * + * @return int + */ + public function getVelmul() + { + return $this->velmul; + } + + /** + * Set router. + * + * @param string|null $router + * + * @return Aulas + */ + public function setRouter($router = null) + { + $this->router = $router; + + return $this; + } + + /** + * Get router. + * + * @return string|null + */ + public function getRouter() + { + return $this->router; + } + + /** + * Set netmask. + * + * @param string|null $netmask + * + * @return Aulas + */ + public function setNetmask($netmask = null) + { + $this->netmask = $netmask; + + return $this; + } + + /** + * Get netmask. + * + * @return string|null + */ + public function getNetmask() + { + return $this->netmask; + } + + /** + * Set dns. + * + * @param string|null $dns + * + * @return Aulas + */ + public function setDns($dns = null) + { + $this->dns = $dns; + + return $this; + } + + /** + * Get dns. + * + * @return string|null + */ + public function getDns() + { + return $this->dns; + } + + /** + * Set proxy. + * + * @param string|null $proxy + * + * @return Aulas + */ + public function setProxy($proxy = null) + { + $this->proxy = $proxy; + + return $this; + } + + /** + * Get proxy. + * + * @return string|null + */ + public function getProxy() + { + return $this->proxy; + } + + /** + * Set ntp. + * + * @param string|null $ntp + * + * @return Aulas + */ + public function setNtp($ntp = null) + { + $this->ntp = $ntp; + + return $this; + } + + /** + * Get ntp. + * + * @return string|null + */ + public function getNtp() + { + return $this->ntp; + } + + /** + * Set modp2p. + * + * @param string|null $modp2p + * + * @return Aulas + */ + public function setModp2p($modp2p = null) + { + $this->modp2p = $modp2p; + + return $this; + } + + /** + * Get modp2p. + * + * @return string|null + */ + public function getModp2p() + { + return $this->modp2p; + } + + /** + * Set timep2p. + * + * @param int $timep2p + * + * @return Aulas + */ + public function setTimep2p($timep2p) + { + $this->timep2p = $timep2p; + + return $this; + } + + /** + * Get timep2p. + * + * @return int + */ + public function getTimep2p() + { + return $this->timep2p; + } + + /** + * Set validacion. + * + * @param bool|null $validacion + * + * @return Aulas + */ + public function setValidacion($validacion = null) + { + $this->validacion = $validacion; + + return $this; + } + + /** + * Get validacion. + * + * @return bool|null + */ + public function getValidacion() + { + return $this->validacion; + } + + /** + * Set paginalogin. + * + * @param string|null $paginalogin + * + * @return Aulas + */ + public function setPaginalogin($paginalogin = null) + { + $this->paginalogin = $paginalogin; + + return $this; + } + + /** + * Get paginalogin. + * + * @return string|null + */ + public function getPaginalogin() + { + return $this->paginalogin; + } + + /** + * Set paginavalidacion. + * + * @param string|null $paginavalidacion + * + * @return Aulas + */ + public function setPaginavalidacion($paginavalidacion = null) + { + $this->paginavalidacion = $paginavalidacion; + + return $this; + } + + /** + * Get paginavalidacion. + * + * @return string|null + */ + public function getPaginavalidacion() + { + return $this->paginavalidacion; + } + + /** + * Set inremotepc. + * + * @param bool|null $inremotepc + * + * @return Aulas + */ + public function setInremotepc($inremotepc = null) + { + $this->inremotepc = $inremotepc; + + return $this; + } + + /** + * Get inremotepc. + * + * @return bool|null + */ + public function getInremotepc() + { + return $this->inremotepc; + } + + /** + * Set oglivedir. + * + * @param string $oglivedir + * + * @return Aulas + */ + public function setOglivedir($oglivedir) + { + $this->oglivedir = $oglivedir; + + return $this; + } + + /** + * Get oglivedir. + * + * @return string + */ + public function getOglivedir() + { + return $this->oglivedir; + } + + /** + * Get idaula. + * + * @return int + */ + public function getIdaula() + { + return $this->idaula; + } +} diff --git a/src/Entity/Migration/Centros.php b/src/Entity/Migration/Centros.php new file mode 100644 index 0000000..1978a02 --- /dev/null +++ b/src/Entity/Migration/Centros.php @@ -0,0 +1,133 @@ +nombrecentro = $nombrecentro; + + return $this; + } + + /** + * Get nombrecentro. + * + * @return string + */ + public function getNombrecentro() + { + return $this->nombrecentro; + } + + /** + * Set identidad. + * + * @param int|null $identidad + * + * @return Centros + */ + public function setIdentidad($identidad = null) + { + $this->identidad = $identidad; + + return $this; + } + + /** + * Get identidad. + * + * @return int|null + */ + public function getIdentidad() + { + return $this->identidad; + } + + /** + * Set comentarios. + * + * @param string|null $comentarios + * + * @return Centros + */ + public function setComentarios($comentarios = null) + { + $this->comentarios = $comentarios; + + return $this; + } + + /** + * Get comentarios. + * + * @return string|null + */ + public function getComentarios() + { + return $this->comentarios; + } + + /** + * Set directorio. + * + * @param string|null $directorio + * + * @return Centros + */ + public function setDirectorio($directorio = null) + { + $this->directorio = $directorio; + + return $this; + } + + /** + * Get directorio. + * + * @return string|null + */ + public function getDirectorio() + { + return $this->directorio; + } + + /** + * Get idcentro. + * + * @return int + */ + public function getIdcentro() + { + return $this->idcentro; + } +} diff --git a/src/Entity/Migration/Gruposordenadores.php b/src/Entity/Migration/Gruposordenadores.php new file mode 100644 index 0000000..d2a27b4 --- /dev/null +++ b/src/Entity/Migration/Gruposordenadores.php @@ -0,0 +1,133 @@ +nombregrupoordenador = $nombregrupoordenador; + + return $this; + } + + /** + * Get nombregrupoordenador. + * + * @return string + */ + public function getNombregrupoordenador() + { + return $this->nombregrupoordenador; + } + + /** + * Set idaula. + * + * @param int $idaula + * + * @return Gruposordenadores + */ + public function setIdaula($idaula) + { + $this->idaula = $idaula; + + return $this; + } + + /** + * Get idaula. + * + * @return int + */ + public function getIdaula() + { + return $this->idaula; + } + + /** + * Set grupoid. + * + * @param int|null $grupoid + * + * @return Gruposordenadores + */ + public function setGrupoid($grupoid = null) + { + $this->grupoid = $grupoid; + + return $this; + } + + /** + * Get grupoid. + * + * @return int|null + */ + public function getGrupoid() + { + return $this->grupoid; + } + + /** + * Set comentarios. + * + * @param string|null $comentarios + * + * @return Gruposordenadores + */ + public function setComentarios($comentarios = null) + { + $this->comentarios = $comentarios; + + return $this; + } + + /** + * Get comentarios. + * + * @return string|null + */ + public function getComentarios() + { + return $this->comentarios; + } + + /** + * Get idgrupo. + * + * @return int + */ + public function getIdgrupo() + { + return $this->idgrupo; + } +} diff --git a/src/Entity/Migration/Ordenadores.php b/src/Entity/Migration/Ordenadores.php new file mode 100644 index 0000000..351defe --- /dev/null +++ b/src/Entity/Migration/Ordenadores.php @@ -0,0 +1,663 @@ +nombreordenador = $nombreordenador; + + return $this; + } + + /** + * Get nombreordenador. + * + * @return string|null + */ + public function getNombreordenador() + { + return $this->nombreordenador; + } + + /** + * Set numserie. + * + * @param string|null $numserie + * + * @return Ordenadores + */ + public function setNumserie($numserie = null) + { + $this->numserie = $numserie; + + return $this; + } + + /** + * Get numserie. + * + * @return string|null + */ + public function getNumserie() + { + return $this->numserie; + } + + /** + * Set ip. + * + * @param string $ip + * + * @return Ordenadores + */ + public function setIp($ip) + { + $this->ip = $ip; + + return $this; + } + + /** + * Get ip. + * + * @return string + */ + public function getIp() + { + return $this->ip; + } + + /** + * Set mac. + * + * @param string|null $mac + * + * @return Ordenadores + */ + public function setMac($mac = null) + { + $this->mac = $mac; + + return $this; + } + + /** + * Get mac. + * + * @return string|null + */ + public function getMac() + { + return $this->mac; + } + + /** + * Set idaula. + * + * @param int|null $idaula + * + * @return Ordenadores + */ + public function setIdaula($idaula = null) + { + $this->idaula = $idaula; + + return $this; + } + + /** + * Get idaula. + * + * @return int|null + */ + public function getIdaula() + { + return $this->idaula; + } + + /** + * Set idperfilhard. + * + * @param int|null $idperfilhard + * + * @return Ordenadores + */ + public function setIdperfilhard($idperfilhard = null) + { + $this->idperfilhard = $idperfilhard; + + return $this; + } + + /** + * Get idperfilhard. + * + * @return int|null + */ + public function getIdperfilhard() + { + return $this->idperfilhard; + } + + /** + * Set idrepositorio. + * + * @param int|null $idrepositorio + * + * @return Ordenadores + */ + public function setIdrepositorio($idrepositorio = null) + { + $this->idrepositorio = $idrepositorio; + + return $this; + } + + /** + * Get idrepositorio. + * + * @return int|null + */ + public function getIdrepositorio() + { + return $this->idrepositorio; + } + + /** + * Set grupoid. + * + * @param int|null $grupoid + * + * @return Ordenadores + */ + public function setGrupoid($grupoid = null) + { + $this->grupoid = $grupoid; + + return $this; + } + + /** + * Get grupoid. + * + * @return int|null + */ + public function getGrupoid() + { + return $this->grupoid; + } + + /** + * Set idmenu. + * + * @param int|null $idmenu + * + * @return Ordenadores + */ + public function setIdmenu($idmenu = null) + { + $this->idmenu = $idmenu; + + return $this; + } + + /** + * Get idmenu. + * + * @return int|null + */ + public function getIdmenu() + { + return $this->idmenu; + } + + /** + * Set cache. + * + * @param int|null $cache + * + * @return Ordenadores + */ + public function setCache($cache = null) + { + $this->cache = $cache; + + return $this; + } + + /** + * Get cache. + * + * @return int|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * Set router. + * + * @param string $router + * + * @return Ordenadores + */ + public function setRouter($router) + { + $this->router = $router; + + return $this; + } + + /** + * Get router. + * + * @return string + */ + public function getRouter() + { + return $this->router; + } + + /** + * Set mascara. + * + * @param string $mascara + * + * @return Ordenadores + */ + public function setMascara($mascara) + { + $this->mascara = $mascara; + + return $this; + } + + /** + * Get mascara. + * + * @return string + */ + public function getMascara() + { + return $this->mascara; + } + + /** + * Set idproautoexec. + * + * @param int $idproautoexec + * + * @return Ordenadores + */ + public function setIdproautoexec($idproautoexec) + { + $this->idproautoexec = $idproautoexec; + + return $this; + } + + /** + * Get idproautoexec. + * + * @return int + */ + public function getIdproautoexec() + { + return $this->idproautoexec; + } + + /** + * Set arranque. + * + * @param string $arranque + * + * @return Ordenadores + */ + public function setArranque($arranque) + { + $this->arranque = $arranque; + + return $this; + } + + /** + * Get arranque. + * + * @return string + */ + public function getArranque() + { + return $this->arranque; + } + + /** + * Set netiface. + * + * @param string|null $netiface + * + * @return Ordenadores + */ + public function setNetiface($netiface = null) + { + $this->netiface = $netiface; + + return $this; + } + + /** + * Get netiface. + * + * @return string|null + */ + public function getNetiface() + { + return $this->netiface; + } + + /** + * Set netdriver. + * + * @param string $netdriver + * + * @return Ordenadores + */ + public function setNetdriver($netdriver) + { + $this->netdriver = $netdriver; + + return $this; + } + + /** + * Get netdriver. + * + * @return string + */ + public function getNetdriver() + { + return $this->netdriver; + } + + /** + * Set fotoord. + * + * @param string $fotoord + * + * @return Ordenadores + */ + public function setFotoord($fotoord) + { + $this->fotoord = $fotoord; + + return $this; + } + + /** + * Get fotoord. + * + * @return string + */ + public function getFotoord() + { + return $this->fotoord; + } + + /** + * Set validacion. + * + * @param bool|null $validacion + * + * @return Ordenadores + */ + public function setValidacion($validacion = null) + { + $this->validacion = $validacion; + + return $this; + } + + /** + * Get validacion. + * + * @return bool|null + */ + public function getValidacion() + { + return $this->validacion; + } + + /** + * Set paginalogin. + * + * @param string|null $paginalogin + * + * @return Ordenadores + */ + public function setPaginalogin($paginalogin = null) + { + $this->paginalogin = $paginalogin; + + return $this; + } + + /** + * Get paginalogin. + * + * @return string|null + */ + public function getPaginalogin() + { + return $this->paginalogin; + } + + /** + * Set paginavalidacion. + * + * @param string|null $paginavalidacion + * + * @return Ordenadores + */ + public function setPaginavalidacion($paginavalidacion = null) + { + $this->paginavalidacion = $paginavalidacion; + + return $this; + } + + /** + * Get paginavalidacion. + * + * @return string|null + */ + public function getPaginavalidacion() + { + return $this->paginavalidacion; + } + + /** + * Set agentkey. + * + * @param string|null $agentkey + * + * @return Ordenadores + */ + public function setAgentkey($agentkey = null) + { + $this->agentkey = $agentkey; + + return $this; + } + + /** + * Get agentkey. + * + * @return string|null + */ + public function getAgentkey() + { + return $this->agentkey; + } + + /** + * Set oglivedir. + * + * @param string $oglivedir + * + * @return Ordenadores + */ + public function setOglivedir($oglivedir) + { + $this->oglivedir = $oglivedir; + + return $this; + } + + /** + * Get oglivedir. + * + * @return string + */ + public function getOglivedir() + { + return $this->oglivedir; + } + + /** + * Get idordenador. + * + * @return int + */ + public function getIdordenador() + { + return $this->idordenador; + } +} diff --git a/src/Entity/NameableTrait.php b/src/Entity/NameableTrait.php index 5510771..fdeadfa 100644 --- a/src/Entity/NameableTrait.php +++ b/src/Entity/NameableTrait.php @@ -2,6 +2,8 @@ namespace App\Entity; +use Doctrine\ORM\Mapping as ORM; + trait NameableTrait { #[ORM\Column(length: 255)] diff --git a/src/Entity/OrganizationalUnit.php b/src/Entity/OrganizationalUnit.php index 6de54da..014c847 100644 --- a/src/Entity/OrganizationalUnit.php +++ b/src/Entity/OrganizationalUnit.php @@ -2,14 +2,14 @@ namespace App\Entity; -use App\Repository\OrganizationalUnitRepository; use Gedmo\Mapping\Annotation as Gedmo; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; +use Gedmo\Tree\Entity\Repository\MaterializedPathRepository; -#[Gedmo\Tree(type: 'materialized-path')] -#[ORM\Entity(repositoryClass: OrganizationalUnitRepository::class)] +#[Gedmo\Tree(type: 'materializedPath')] +#[ORM\Entity(repositoryClass: MaterializedPathRepository::class)] class OrganizationalUnit extends AbstractEntity { use NameableTrait; @@ -20,10 +20,19 @@ class OrganizationalUnit extends AbstractEntity #[ORM\Column(length: 255, nullable: true)] private ?string $comments = null; - #[Gedmo\TreePath(separator: '/', appendId: true)] + #[Gedmo\TreePath(separator: '/', appendId: false, startsWithSeparator: true, endsWithSeparator: false)] #[ORM\Column(length: 255, nullable: true)] private ?string $path = null; + #[Gedmo\TreeLevel] + #[ORM\Column(length: 255, nullable: true)] + private ?int $level; + + #[Gedmo\TreePathSource] + #[Gedmo\Slug(fields: ['name'])] + #[ORM\Column(length: 255, nullable: true)] + private ?string $slug = null; + #[Gedmo\TreeParent] #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'organizationalUnits')] private ?self $parent = null; @@ -31,7 +40,7 @@ class OrganizationalUnit extends AbstractEntity /** * @var Collection */ - #[ORM\OneToMany(targetEntity: self::class, mappedBy: 'parent')] + #[ORM\OneToMany(mappedBy: 'parent', targetEntity: self::class)] private Collection $organizationalUnits; #[ORM\ManyToOne(inversedBy: 'organizationalUnits')] @@ -43,11 +52,18 @@ class OrganizationalUnit extends AbstractEntity #[ORM\ManyToMany(targetEntity: User::class, mappedBy: 'allowedOrganizationalUnits')] private Collection $users; + /** + * @var Collection + */ + #[ORM\OneToMany(mappedBy: 'organizationalUnit', targetEntity: Client::class)] + private Collection $clients; + public function __construct() { parent::__construct(); $this->organizationalUnits = new ArrayCollection(); $this->users = new ArrayCollection(); + $this->clients = new ArrayCollection(); } public function getDescription(): ?string @@ -86,6 +102,30 @@ class OrganizationalUnit extends AbstractEntity return $this; } + public function getLevel(): ?int + { + return $this->level; + } + + public function setLevel(?int $level): static + { + $this->level = $level; + + return $this; + } + + public function getSlug(): ?string + { + return $this->slug; + } + + public function setSlug(?string $slug): static + { + $this->slug = $slug; + + return $this; + } + public function getParent(): ?self { return $this->parent; @@ -166,4 +206,33 @@ class OrganizationalUnit extends AbstractEntity return $this; } + + /** + * @return Collection + */ + public function getClients(): Collection + { + return $this->clients; + } + + public function addClient(Client $client): static + { + if (!$this->clients->contains($client)) { + $this->clients->add($client); + $client->setOrganizationalUnit($this); + } + + return $this; + } + + public function removeClient(Client $client): static + { + if ($this->clients->removeElement($client)) { + if ($client->getOrganizationalUnit() === $this) { + $client->setOrganizationalUnit(null); + } + } + + return $this; + } } diff --git a/src/Entity/ToggleableTrait.php b/src/Entity/ToggleableTrait.php index 5e724da..7bce1d3 100644 --- a/src/Entity/ToggleableTrait.php +++ b/src/Entity/ToggleableTrait.php @@ -2,6 +2,8 @@ namespace App\Entity; +use Doctrine\ORM\Mapping as ORM; + trait ToggleableTrait { #[ORM\Column(type: 'boolean')] diff --git a/src/Entity/User.php b/src/Entity/User.php index 2349dd7..e7f4c6d 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -91,7 +91,7 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate foreach ($this->getUserGroups() as $userGroup) { if ($userGroup->isEnabled()){ - $roles = array_merge($roles, $userGroup->getRoles()); + $roles = array_merge($roles, $userGroup->getPermissions()); } } @@ -178,6 +178,17 @@ class User extends AbstractEntity implements UserInterface, PasswordAuthenticate return $this->allowedOrganizationalUnits; } + public function setAllowedOrganizationalUnits(array $allowedOrganizationalUnits): static + { + $this->allowedOrganizationalUnits->clear(); + + foreach ($allowedOrganizationalUnits as $allowedOrganizationalUnit){ + $this->addAllowedOrganizationalUnit($allowedOrganizationalUnit); + } + + return $this; + } + public function addAllowedOrganizationalUnit(OrganizationalUnit $allowedOrganizationalUnit): static { if (!$this->allowedOrganizationalUnits->contains($allowedOrganizationalUnit)) { diff --git a/src/Entity/UserGroup.php b/src/Entity/UserGroup.php index 32c8ce4..a017aa9 100644 --- a/src/Entity/UserGroup.php +++ b/src/Entity/UserGroup.php @@ -14,11 +14,9 @@ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; #[UniqueEntity(fields: ['name'], message: 'There is already an role with this name')] class UserGroup extends AbstractEntity { + use NameableTrait; use ToggleableTrait; - #[ORM\Column(length: 255)] - private ?string $name = null; - #[ORM\Column(type: Types::JSON)] private array $permissions = []; @@ -35,18 +33,6 @@ class UserGroup extends AbstractEntity $this->users = new ArrayCollection(); } - public function getName(): ?string - { - return $this->name; - } - - public function setName(string $name): static - { - $this->name = $name; - - return $this; - } - public function getPermissions(): array { return $this->permissions; diff --git a/src/Repository/ClientRepository.php b/src/Repository/ClientRepository.php new file mode 100644 index 0000000..81c3e7f --- /dev/null +++ b/src/Repository/ClientRepository.php @@ -0,0 +1,43 @@ + + */ +class ClientRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Client::class); + } + + // /** + // * @return Client[] Returns an array of Client objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('c.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Client + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/OrganizationalUnitRepository.php b/src/Repository/OrganizationalUnitRepository.php index 591f292..566214c 100644 --- a/src/Repository/OrganizationalUnitRepository.php +++ b/src/Repository/OrganizationalUnitRepository.php @@ -9,35 +9,10 @@ use Doctrine\Persistence\ManagerRegistry; /** * @extends ServiceEntityRepository */ -class OrganizationalUnitRepository extends ServiceEntityRepository +class OrganizationalUnitRepository extends AbstractRepository { public function __construct(ManagerRegistry $registry) { parent::__construct($registry, OrganizationalUnit::class); } - - // /** - // * @return OrganizationalUnit[] Returns an array of OrganizationalUnit objects - // */ - // public function findByExampleField($value): array - // { - // return $this->createQueryBuilder('o') - // ->andWhere('o.exampleField = :val') - // ->setParameter('val', $value) - // ->orderBy('o.id', 'ASC') - // ->setMaxResults(10) - // ->getQuery() - // ->getResult() - // ; - // } - - // public function findOneBySomeField($value): ?OrganizationalUnit - // { - // return $this->createQueryBuilder('o') - // ->andWhere('o.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } } diff --git a/src/State/Processor/OrganizationalUnitProcessor.php b/src/State/Processor/OrganizationalUnitProcessor.php new file mode 100644 index 0000000..4dd6aac --- /dev/null +++ b/src/State/Processor/OrganizationalUnitProcessor.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 = []): OrganizationalUnitOutput + { + if (!($data instanceof OrganizationalUnitOutput)) { + throw new \Exception(sprintf('data is not instance of %s', OrganizationalUnitInput::class)); + } + + $entity = null; + if (isset($uriVariables['uuid'])) { + $entity = $this->organizationalUnitRepository->findOneByUuid($uriVariables['uuid']); + } + + $organizationalUnit = $data->createOrUpdateEntity($entity); + $this->validator->validate($organizationalUnit); + $this->organizationalUnitRepository->save($organizationalUnit); + + return new OrganizationalUnitOutput($organizationalUnit); + } + + private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null + { + $user = $this->organizationalUnitRepository->findOneByUuid($uriVariables['uuid']); + $this->organizationalUnitRepository->delete($user); + + return null; + } +} diff --git a/src/State/Processor/UserGroupProcessor.php b/src/State/Processor/UserGroupProcessor.php index 0dfdaea..e45900b 100644 --- a/src/State/Processor/UserGroupProcessor.php +++ b/src/State/Processor/UserGroupProcessor.php @@ -10,7 +10,6 @@ use ApiPlatform\Metadata\Put; use ApiPlatform\State\ProcessorInterface; use ApiPlatform\Validator\ValidatorInterface; use App\Dto\Input\UserGroupInput; -use App\Dto\Input\UserInput; use App\Dto\Output\UserGroupOutput; use App\Repository\UserGroupRepository; diff --git a/src/State/Provider/OrganizationalUnitProvider.php b/src/State/Provider/OrganizationalUnitProvider.php new file mode 100644 index 0000000..83d5735 --- /dev/null +++ b/src/State/Provider/OrganizationalUnitProvider.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 OrganizationalUnitOutput($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('Organizational unit not found'); + } + + return new OrganizationalUnitOutput($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 UserGroupInput($item) : null; + } + + return new UserGroupInput(); + } +} From 2a2c2bcfe0cb6c774d5210bd7ab63481ca3b5b93 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 29 May 2024 09:16:58 +0200 Subject: [PATCH 10/15] refs #379. Add validation in User CRUD --- config/api_platform/User.yaml | 2 +- src/Dto/Input/UserGroupInput.php | 2 +- src/Dto/Input/UserInput.php | 4 ++-- src/Dto/Output/UserOutput.php | 3 +++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index 17e4801..427c416 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -21,7 +21,7 @@ resources: ApiPlatform\Metadata\Patch: provider: App\State\Provider\UserProvider ApiPlatform\Metadata\Post: - validation_context: + validationContext: groups: [ 'default', 'user:post' ] ApiPlatform\Metadata\Delete: ~ diff --git a/src/Dto/Input/UserGroupInput.php b/src/Dto/Input/UserGroupInput.php index d26ef2a..71047cf 100644 --- a/src/Dto/Input/UserGroupInput.php +++ b/src/Dto/Input/UserGroupInput.php @@ -13,7 +13,7 @@ final class UserGroupInput public ?string $name = null; #[Groups(['user-group:write'])] - public array $permissions = []; + public ?array $permissions = []; #[Assert\NotNull] #[Groups(['user-group:write'])] diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index 379c5f9..79394c9 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -23,8 +23,8 @@ final class UserInput #[Groups(['user:write'])] public array $allowedOrganizationalUnits = []; - #[Assert\NotBlank(groups: ['user:create'])] - #[Assert\Length(min: 8)] + #[Assert\NotBlank(groups: ['user:post'])] + #[Assert\Length(min: 8, groups: ['user:write', 'user:post'])] #[Groups(['user:write'])] public ?string $password = null; diff --git a/src/Dto/Output/UserOutput.php b/src/Dto/Output/UserOutput.php index ec66afe..5cd4d72 100644 --- a/src/Dto/Output/UserOutput.php +++ b/src/Dto/Output/UserOutput.php @@ -19,6 +19,8 @@ final class UserOutput extends AbstractOutput public bool $enabled; #[Groups(['user:read'])] public array $allowedOrganizationalUnits; + #[Groups(['user:read'])] + public array $userGroups; #[Groups(['user:read'])] public \DateTime $createAt; @@ -33,6 +35,7 @@ final class UserOutput extends AbstractOutput $this->username = $user->getUsername(); $this->roles = $user->getRoles(); $this->enabled = $user->isEnabled(); + $this->userGroups = $user->getUserGroups()->toArray(); $this->allowedOrganizationalUnits = $user->getAllowedOrganizationalUnits()->toArray(); $this->createAt = $user->getCreatedAt(); $this->createBy = $user->getCreatedBy(); From 82ea76a557b03e8d522419185f7f2b69f641e8bd Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 29 May 2024 12:07:09 +0200 Subject: [PATCH 11/15] refs #380. Modify Readme.md --- README.md | 28 +++++++++++++++++++++++++++- swagger-assets/img.png | Bin 0 -> 39659 bytes swagger-assets/img2.png | Bin 0 -> 121465 bytes swagger-assets/img3.png | Bin 0 -> 132956 bytes 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 swagger-assets/img.png create mode 100644 swagger-assets/img2.png create mode 100644 swagger-assets/img3.png diff --git a/README.md b/README.md index 37c0e4f..0328408 100644 --- a/README.md +++ b/README.md @@ -61,9 +61,26 @@ docker exec ogcore-php php bin/console doctrine:migrations:migrate --no-interact ```sh docker exec ogcore-php php bin/console doctrine:fixtures:load --no-interaction docker exec ogcore-php php bin/console app:load-default-user-groups - ``` +## UX Api Platform + +Api Platform proporciona una interfaz de usuario para interactuar con la API de ogCore. Para acceder a la interfaz de usuario, accede a la siguiente URL: + +```sh +http://127.0.0.1:8080/api/docs +``` + +Para poder autenticarte, necesitas un token JWT. Para obtenerlo, accedemos al endpoint de autenticación "auth/login": + +![img.png](swagger-assets/img.png) +![img.png](swagger-assets/img2.png) +Obtenemos el token y lo introducimos en la interfaz de usuario de Api Platform de la siguiente manera: + +![img.png](swagger-assets/img3.png) + +Ahora, ya podemos interactuar con la API de ogCore. Para comprobar que todo está correcto, podemos fijarnos en los headers de las llamadas Curl, y ver que el token JWT se ha introducido correctamente. + ## Test Para ejecutar los test, ejecutamos el siguiente comando: @@ -71,3 +88,12 @@ Para ejecutar los test, ejecutamos el siguiente comando: ```sh docker compose exec php bin/phpunit ``` + +## Reiniciar base de datos + +Es posible que en momentos de desarrollo, sea necesario volver a cargar la base de datos y actualizar los esquemas de la misma, para ello, ejecutamos el siguiente comando: + +```sh +docker exec ogcore-php php bin/console doctrine:database:drop --force +docker exec ogcore-php php bin/console doctrine:database:create +``` diff --git a/swagger-assets/img.png b/swagger-assets/img.png new file mode 100644 index 0000000000000000000000000000000000000000..6673bfced069157cb815404e387c0189e522079c GIT binary patch literal 39659 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 literal 0 HcmV?d00001 diff --git a/swagger-assets/img2.png b/swagger-assets/img2.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4241c961dfa4074ea1f88308d1a952b920bc3f GIT binary patch literal 121465 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%0DCbSSsP@VX8NNnY!O)+`+`v6= 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�^C}D+H(oKeiV9I_o#Nv&7DTJlWfr@X?ttSN+j_X8;x)-8Czs1!vINU zc%#rijNw31WAKfafDK~qtR}L8Iou78dcmOZRvWWt_K&kBfOpAcxqaPfB`^jdcUqzg z*S&LLy4L)ABhrb$-ttY*4i>14ZLo5$$Uqm|g`Ar|^nLg{Je-WW(Fv-Q=|gF({ZM30 zP;Wzag)hb0hUbE8qsI^z&s^Rj_M0YOQ?GW*S7#D1hKH)oes`W3efuKk%$}hpY4 zniYY2s2QoS*wAS4;}!9&M0IrD?)9=ueiDpJR#{BnHR>7Nxil7VzCY*#B#ALnFM4Q( z*ytdU)M8UnXp92w8UE|f6RTe6tFaDiz>0+h&4H;g!(JktFgoJryt9R3yx8d|(JWiR zRqyXiUP><-=uxcbapc3z)KQ<&%X`10v7-mNL(baywl6Ty-fD&^gljCD zp;(q<;m1Imc?`}N`pN#FcKN(rbW3y`a+<~!QV1pU9;189(K7I6G3JTuD!#NkJzxqN zQC2bdBADoLdvp0nsBA7@d;r($r=fHV|OO8fp3-XGq%xVLpN%P<*A^wDC5IK zRJm6Hs3hI3SA=6P>AalG~>7`0$fLHUg0LIJz zQ&WI4TL!C^oB8<_RwiI3w~C2A07+S9vXR}_RtCT%Tt6m1R@%6vJ6~~5V&syvA1E?0 zC(TRK36>g|a@F^7{YOlt2=)LYAK^kQW5n%c_IKUyfa?06!D>u*pPTEcap|9d*Xbxn9u{Z7 zNH~GV;bqEfDnl}x3Lc7%l{lgn#B~6g9w{Y^w%_g)*{h!ZK-&d>`^q1A%oF7|M!N-) zZ3c=I4@_==r;k|z-*>bzG8|HgvJ7qX{4D64(i^0$XVx2Zn3fK9r<1Ey-WlS3rYhO< zPsyse!*mmt?_|ZUM8;rT2G>t%NES1m0HrA7+I!My{P?wKRuzu&okcsB#^s*y#q)AO-= zW=q5-)`4}c$PkP{ohFqZ)#&Cwn2#x(<*&D4F3Qr}jf5I&&2ru_Z}qI`lz$LBm07om zg5&2DT?tpDms0UAGZwe%Dj^S=CN`HPdMZl;-Oh^YW7{9jbINebI;oV8}m{{D$NKDj7Z;4TD@9`E{|g&3W%nX9@E#52kJReX^hC<3y7 zjH5!nfb);;Ns0-TI!!1oif>RImU6|Wm&Cznqzg_pzIT@*-3s5=V41V9x^Zk`(!-16 zeu`C)&_Vvj_`fWad*h8QfYfW_xO~vb+KyW_JRl8p&%5H*yN6er z&gX3jEx6A0Z5w6xq_5IV-974@&HVNaSLy&-_tbFV^Zp6X|4a>$k6A~qFG~g%2FLp@ z@X@c{6vUU@Z7uL>+}1{la-w+n$CqeTHad)eausbme>;xEM~=JuzTfNF-K}O1xkAr1 zWyB5YBRgt>ex6hAVT=g5%Rv=kCpbXCe;JRUU6CCutAtI91?ySTd281M*h+6B@=SdB z97R9`iFp$5k^iI-BR&}es}v!4T%|WF#?K+{!($lyVJbvhnj?cg=akyJG z8E84_1*1y74#n|E0w-!%)5ADe!r<8(eB+RePDuR+Wd~J+t(i)hn%e8VklgXbyo>R2 zb@fhhC1Rx}kkh&qZ#RCdbrDu(Vf1Xwy3h_Fo={cV8Ml8E033OznCd*qeb{@TBxJsW8(7X7Y~*k>J!?;D>LLsW|MiQM=+w)K--#J}V0J z8#Mt%*)X-bnmG+XKlQO(R9!0bupBe0vBLN?3z)>2fmy>IEsp`x#KEt(dzm-FZ#5q7 z>bm-lQq}tsq2>IkHa|Qmyzfv{)Mp~{)`-x9$6QfL27!2`S0$_{9-@>~F*ex>zxLA? z*|FoR#ywZcPFen>o5S@UhS>CJSZQwx%Ry&Vfp{OM2K0%`eOwb`rpe|V8D`_wYSQC5 z%sTa>&nZ|lTiv-bF`k_UW?y=B&vawjw)1$-GBaVrQuO)-WKcVbZ&8~03Wn>jbWA~Q zIzt=erV6`O1OX!nMg_SP?n@3Ae1?Z|{^EIF8uUEoH$5wP(xgnq-y1wB_#CGa;-1dD zUhm{Yb-N5O->6>gN@b~5AyFGFy5M>v6P+m_{mXzk4Ml639$C)u?Ij+a9OW~&239BR zP*LVtIkw$m9%n%Znc^XVf=T%(=OR^2&Sf{HQU~2!HJETV%oIGju1!GoJ5&|zIgeG( z$)gnf=}|?&o*oqSk$dJXEUg-gbB5cZ=$ss~MF6eX?RVySNzJmtqwM^NFm{iFA<=82 zgma0#CG-Pl0wF!TUH3H5cd%|i&NOm(rw77FN?_I?4Qoj{ZSlUhypJ&|@NU){y+59g z*L_31J_hk?FU9{N{xio|f01ucJ>~TMh?$9VaDAV7E69BVhWz8|pz(pSTz1{nX;~Sf zao~A|`5ZWUslKXh<*bM4ivF#%wR1q9ewk(N1fw9ZxO=PhR;a zFH5i8j^?t5&&6FSk89ug73%Pqso1H#FGyf*&;~9I#K4=uWE`Q?Uwc^^o64K@QeG(i z(N#ncOhqEV71+{gpeC9KJ(k_)SpS~I3;K4W{6 zj@ik#5fzmwv@+&|Q=Y)SdOrD4fOn&Nm1qG5Mf%!4zt)Zlmm~T&FtFeK#ey=}3y8Q= zk}MqqgE7dza}&_Nk$1#*7`h1Kh0DXpRk*r_#dP%|Ic8&vm%t(H^Y)h5%dD2@_)9g%8+IiZH9@L*9+l)4Rd9QTuVrcD zVgj*`r!XtXGUtT=8AGePz7PL|3$wBguGH5~=8z<|JR)2>IxkW; z*r)+L{?6G8Fh-Hsp_m(wwL=oO`6oRKZgs6BeDebKwCJImRy7eJo2R*;yoqWdsqscS zk9igV<%m^~-O5MqGVB^jA+W`)K}_iLtllY8o$Kn*35{41S5}BFVs+aGb!y?5H0YOz z7~+C9!YPtitef#Qt#CuiTEOEZuilxf8VvAif4ldW#GjcTSNo1A>KRA;$?L&`8Q zC}(Cx$dpt_;0Sx%n}PRQrc*al9VZx-)st`>MA!WcKI|E3sn?)l)?;o+@!7$v3&B(S zaFd~>MsrUVR4^0w^#jb}hlrFza0haX=c#Ljp;R^T_dqz}VOF-%Raj{e+?h*O+nI#5 z&0NPu45CUAs;Z$`6yo#jb^k7?MlC+Pp_slBv%Sox8iJqa8u>ce(D%iSE-42p%K_hC zZL0|@`i6IwheqsN(zv6)yLA5qyE}P!TW<7(H>a8Os56vK zlzdaxt9%!5)++`=Wg#8?`fBT~w~RWqWm)}84J0K0KA!IWxXO0<<3|r(5VB$c`y?&C#4~1X15DQ{THE^7(f`=v&y#pLBgC zunA@LXXCi(RUxB5%gI&Y$iWm@|p*Lo06_OFn>yp3CU?w9^IA=(Q-hGY?ux%Q7=K(fERZ37Qd^U z(y*mUKIWwqeVLcG1gfiW}8R_E0*E-!-{B?yhvb`mqIvSnriFQdbVwO55?=; z-2fQbmO~q)*KkXUNhvgQ)Z(qXr>A>zf#fr$(of;hr~~hfpL>k0jBQP;aUqT(qcfKQ zPkOktUF8mUUBy_37N6wB!7BfbrPNFLZ38@psuEU2Hn??#in++k5)Wh~=+&|x2Z!q} z>#YDK7n!y!0>Zf@wN4pMzt7L~zVc#uVC9oun#i`CbqT?%XA@X>IM%hikt$~I;UEpc zx&)Twz=^dB?U!tmk)YHbAw&{P>%yPDhG85NE==%#bkg!2tJG$Sa`*(Pb9J+2tW4u% z!iVVKXh6Y?ev)6GL`uVzH%2?`P?3J6CMw*zBYvZvW{L2A^lH$XV3_I?MI1ouuM+DM z%rrMd{3F)?;FrFnx!iVOV2?1Zs>g+q8fQ(2P#K9Mp`!(BL(z$GTYpvRkY|k%=Ptcb zfgjX=(illgiXP)@Lhz)Xpn6@mVR_>47e1K22gGQ^_h6=t^qy3d)R5Rxt#WurwLhy$ zK*r&ljK_ykq(E{`Qw{D~?unI`%Yb@IZM0m1z{f5vR@8{OVfe$nEq$sQ+^(KStgBNr zKR3eZ{F0s|;Rx(pdv4m_2-jd@0&N78)*(2XjOQl>9QDJ|+&pS`nye^i))PNC1^-m} zGH@B7^2)7q=g=66hNSvTNTPPGBxJRa!8BuC3sZb+HvW2P3(&A4u6B+njQRb9lz*GT z9t2Kov405&=5|hD;kF7f>+;CbV}ee_aZoZM2g|u>olXW`}>q z7+aa30$&1N_e=^wPZxsIG5RRK(c(~;KHuZS?;iu8FNpnLql_&#pPB9`sP~bA6wlOA zIdjT73>&VQ?C6O42PtJAd*hwu;JUhQt&G1rLn+TJ-r^sinE52L^9lz-_V(W&C|uQ+ zb8?jH{78APW|jkL*`iA;3ZU&Ma1WCUvxp|2(=|?W+$rFuud?I`j4a;O;|$tnT##Py zTN?RuAmiyKTc>Wl8#y1>h6$qZY@kv)MICTmJ*k6 zxqZ9Kl^0VZqHp-bqO@xgeL;7iZ*@#8r%o$Tv${dDz|G z0cBHSQ<=IjsR?IX$zjLW1It?3GrOXaY8Qa5y$yl#;tPmp(eK=cd)CW-g3 z=%at}u=syC(QBPIzqgL&7^a z&ElyYcE@r9Oz_%%F(bd|Ypzv+?FsKhUBH-AVzCFqWI1j+{!L`81JQ88>mrBknT#VfS?dlI zoR(}opUr*$8j%7~*+_f3K7aXQ>A!#;tQ&}DOuw}N2JEL z;ICG`ALJAdaC4A_rLo|>XsAErXUmjg#xez(pJx(*@_F3gq`-QPeXF%K6y^Iv@4x8h zQno1ee8E#44*AoYjE4T0bZuhE3s5c~vk<-P=C_dc$!jf;`rNGj$cm(h`@^pC@_SNc z#KaFnhPKv1SlR7c|I_p$E>B&C-wioKIGD~|S@nXMw>7*pQe0}HX5wR%DpvGPJ;3L2 z@UXw3$7A`~{Kn#{vkYodI? zLx4C18M_ZoYvMEq)^tw`B_0U_jb<-o*8DoM8OCDS+3r41+TmH|Tk zLKWvfg*|qPSjQB#Yyr}?00DUqRz9kC<&JRoN-u zD^#XhXgN_^)Gbm;nV9z6YuF=(!_ZmAGvXCV(9HxZYR=cKC5tnkN|MTjtMs;K2H%}) z*|o)KGe_LeHtq?CxIdCkDNapw8(C=qP9MJwIp*(XP_tI4LZy1&Wbzl9$pIyFgUHK8v= zxGFN?jp7CePq@6_-jr%JI6G+K`=B@vIfj zmOWRS<9(axwKS<{_S&2qMZb=$6%awWbdFQF3!)vT)J#I+IE@Kur^Q)!8XoIZkpEj*$besKb=QLB%2e@Q8yW3an^7_^FI*|vnM{<#UfBw z&z9U6R02$%>FG|=ICr?}@lV!Ed7j*{Z@Sl$?cy)GEu!X|ZMy2G6J#0`#@R7_L}5&* z(+j&~!yLWk2R$;#-v5gkIO6=4jPm{VZyON|NUgr`k>yO89VIIx z^O7Dxefpo5nTz*tJU(wYG)f#RfAM=lQxks0FwBj_W~S>BOAB*^rM&SHJo5!nI5Dse zID(=-``L}*471nq#%2?$Rxb8CjGGP& zirrk}+H`2#R<e6(`!n^Zh9f9QH1Q$-R8 z$(ota2wdI`D~*m3GLlLP+FDm~u<_8|K%a5nXGg?lJ$3ll?)TAI=s>u0(9gJCqSeTX zea__$;ijO8iV}1eOot)so~2JYme!Th`$z;*A+8G~4*0YyS1B{zsB{T=A7X}dB~m`1EFoU?w+0RPFC~^dmz~cXS=t1B_wmZn;NbcKtwGDT;F(4 zk8YF!m9y7^o6rCtQmJJwO2Z#^e72^Y)t$q}|LGs3UQ8~h%j)fwq3n`$i11s4JrR0{ zbFf)T|0=Bfr)1L?`T`tes>e+gq4|6YRE{%f^(}xb#q_-y^|QU5I1Lw(p!t~TEi}Iy z+@l?$i3DM|Wyy|jF^@+WcKq)2iOM zo9?UfQt(9Ltxs-cK)N%!E`F=QPCj^f{q5T)q3rh%R+#{yj@t9m^Fb*K7RjEE!<08f z+@XuZ7G*z;uALkZ(t~))br`r4W-KEa!?~2mTs|3GtgdMIQ z`*xpN!2&DdLr$Mw=j?LbbL!M~v3o{4Zgd2FlaVfgI2VB=HQ$bZk%60W=OOHB9-@%o} z6^AR^bQ4r_d*>!9ZGM=>1oBx;DsE+icJ$PLz*eJPVKeqN`?kH^arSBKnb6|zKHBO6 zes`=@)Vhb^pbzz4_8Efb{IV!a;{N<_iZsS%?U(i;XgLaELc5q{5mot4_%+phMGAc# zmH@O56iUrNZGT*9i5XsIr*KY-n~$ZZ2za#Qen5lOraNlXYs)J4l`GeGNpbrVlLPI9 zg&w3AY!Nl;`}bNjwOStRC$y-FQD3mX9zo8&Omx(jk!we={YKMJxX&cIpWi-SNeK!_^mqVZtakql3aIEATOJ*c4Yr& zbDCkUSGO=)yVsdjFNHLmrQs1AU%NMB#5+LhPt83?&ce^vqV^<7-b7xL(;I}ldKg0^ zAmS7BvZ96fT;9|~qT3MRB2iqyXNIWnBFx^;m&tMEzM2OxhZo@7fSsGlr<$PssqlJg zKb?eMPnGuODTlsn$`}Rij_yFNL(}&3kOJHN7*;wgkzai~O1^zqSUwuWNEeDR4sNCw zOF}8&x9Z^^iz*&U*|_EX3aTE#^ND(GX5v=+`xEEvG0Gy%N0MvyAziD7kgIJwwAZ}f z%TsAxXOZ;c`Dcc}SQ4Ed;HXmz9HFt1q3Wn;m@f382|IergJv}6*a~MaTdX;3?k#|} ze!nW&rWMj_>0$&`*r+89Zqz4^^7XH3I$=aHC$7sWM0)*Vhh-hxV-|#UJB>iP11bZk6Bm-i*$Xp$41g-YZu?> zrehxOpE$i`Ix}V}U?*67n8JqArd{H}z5H&tAC{VGIVn-@G80o6(Qb&LAX6sB3tzXV ziDREA-8*P0$aQhK`~SGMkbcDo(e*O-X8=^f?99d#nK%(-Zn&9}iPdGA=~%TOUEVmI zj$ELad}Z*?>56CP-rDuyS?{hl(l_bch{dN$%`Lpg$wHYxRIUuJM6xUYCSN@o6*=Er z_M|v-VA@^igb5T~G;hd$*s`ImNo#GO4xSGu9L~ckD{jZXVkj|%++dG2Sw>$mO1(F_ z!go?5=dZs}17QndPGEjq6X~V_Se>BYHPVT60U9|SJ5JE@@L4E(^v$;92xDsPgvF%Hw!)pLu7po;oElM;=Lsg= zrkZ1WyAzuM7@mU&UXGp*c+5*ODYwM)#$421sjD=s@llG)dEAVv<=HEZ28&^lCHrmD z7B1H{ut&^%CL>|7sU3$(S1rk^nrPK8;n(GS+YCaIH!n7BjOT3^al7N|CqEzczuCbm zsA@;~zuPleGJZ$gSoM}@O1DR?TC|17rz0{vuB-UEB#Lh9={<341m2|y=+jD z*^S87J^Lg8Nl|Z?SyG`rjXoclS6pJd$%0&4i-A}dhv)!h?ioey*w8xn0O%bI%bJej zD=DoN=|)5PW@sk|g}aq{1WD7tP#^OvWhhH%oPR?K^{?2qg|q+M=ed=^OR4 zJDNO4JVR6b-Eb)eFTWVx&$$FAnWM+Kc5OOf0X$cgrpnzY0d7%g<&)C8x^V7~v07LGhwiVS2FatbKI_i1t~>s~!m z0Kbk7O^CiI<$RM^&f-|`(taXNLfz%`#8sgeQZm2oDd!ttPU%@r|RunYDfspmF` zB0Qped;uZqF$FlE&@mj<`#TfzsG;~JMWuLSSY2nFf%71D^3uUCU(lwfKCFi+~L2U}cZelra_6&Zs}q7a9}JtH?U801@d2vJ`-+#?&crXiwa zedB68Tv`At5r&tn0-o{WX0y_MCeDL(q;SV#vrcR>^9;)-&5|Yvld(oq5c+$Mlk81G z)Wu(A#&28UkGPB!;MKb!lFUQ^o{6LLH^RLWGtzCo3dA$HK#T7?>fFZX z3V~1G>{hCD5ND{1{vCm%BuB8%PK33Ik4&||F$3_UUI8f{Dg!G!Jj34@2aLkivWa2!`wkCP zO=ipLtjC-Aqn+qOPocuHWf#H2yhvxu4u5E$#|70VX%PA?PKldn>IrIPs~4LIiQ{@( z6|wkkE*xz7o%EeD(oVH=yfXUll1|a~_Q|-E9a@fAO5k~3r0eO60_N1OXkMd{MAa=J z5i)^tql9NZr`l3|*|ABq_MMh*IgD}H$ACu;Or^aqCNjSPwVa36Z7W^U8B0ygdN-uc zF5JR$(m8ea^if)fhtHS)bh_>g&-D#%)Smxm9>q98yyfp2Wxu593(C zjxO<#_lHUWzIa zxqCdDwdVBI=&TAKvKv@xSnxDbcb8{gGER<*TuFO;KgG19;uEdSZ>OK{Jv47pOkPj< z3!vpja3({%o*aA;&`^vUeq710(ejw#ikTU#OQ*NWmZ!E7Wuk@O9AHm1nwIF!R@#d5 zgNFXykG&er=H?GJ&=LH$-U0KBr}7PtJJ5}Og-+3&Xye|?fp~Pkk=$*O(D*YeJopRQn5Iq z#Qq6Ebd2cpC)oJ!+AC(sF*{{5_jv%nF&=-MF6F97t9?*P)Ki+~PME>(7~7QX)*!ET zml<+c@~h1j;wfKo=j9p>xGoau6wRL$f8`#eNjBU!(KQC3Zx%>~d^oMoW=$9~yJKsi zGFp9ctTAz8m4=$tHDbS_@5$$z7o*A$GfU}TFS1sRI_-E1bCC>E_L+hN`1c5m~&H^S?HR6h)MOow7J|Fw!0w?2|G0k1JMr_9A)S zu=^2Rzj)Dpaozu$;_nj2W;bTAog!VXOzxu5I$)A=Lv}RePBe8!obEZ~PE$Ua;k^m@ zhHQ}@$_+lsYiwbzQXAydHvgz`5bP@`t=IQ$ORIotMY69k9DSd20Itl6Qg`m3CU)diLNaV&o#3x02DaRhCO6+FX}(5CN~uaeH!{5lPs4d0wj3q4(^54u;BK?pt(b{d zv+sM@E7|vC&oWLR9Q`m4=Z{QqWbyqe0wfRqa|ndVOZ@%Wd&CW@7|lkfohbb_|L9(LX9n_ngRuw&-NP{t;ydPf_A#Qw8<06`4k; z6uP3@5>WZ0^J`q^(;V`8>2Gyi%t^2XVzG#Y`e&3Ol78e8-(r$%w4q?F4XO5})ufv4 z-}c__bsKNe!;}ZFGXfBVSOWf%1i^pDACN9l)EHa%|6zV#?HnG&jl*4jd)CsQ)>ftP7aS&%$Rn+j?dF`TNCr2G1V zYqGGd;?$Rm669+$;4a4$0H^iskPbzcl`adqxU_^x45C=ev2DrmSX1Hl^dBId*CK74 z7K|b4qEX~~p5^PJia6;UYDk5bg*q~@d;c_^Wk6z@Qo|h!=h^ZHeV*>(1nXw{Y1j3Q zvdp2>b{(0AVl->MT&Ck_E^6bhkrK|=Xki_E1Q702t)u4gS$_!+xsn;BK)&%AMCkC!dL|8=xJ zjx(r3q@Ny$kY~jFsL9J8W#(7o;yzJa2B=a7qjn3bNV3M2Y29SGm4oA1pZNzH0O$E+ zx%oCf>*V@L)1Zrc{h7w*_!ONxWbUsln+?UecaYB?YEGr1zN_nlM&9-vBp+ZK2MB0- zl?>5diKXTiMZp$$XiF3FylJU|!?d6#$uw$Y7yzcd{dDsvY#Tt0v!o$T_SG#UXu{P z(qJw2R%8T9-wqaw9!RxVEb56S>q*tEAH%Pnc*^i<|5$AVNs0Kd*cWwN z-WYgjzFAp&MKhSwvWlr%%w1`RI@LUo*R>#lUm})>6qhqDtNzn!4C7hoPHn?&jsjxF z3B0JSF#(9@YH&O|qGI&o9`%bKJ4yA(paeTxmEh-#5cHH>{TU$7f}`-3*%e6$SkL-{ z9;4p6kKl^B${Z+s*kuMHDvg@rwq-?4P%~z-#a4RfxVl+gS0yzrfMNPjtZgQL`=I0l zkdS8vh75Us#9GO#Zpv~&{{A(KBt!BYF^bM|W0}o@+7O?~i*djbwa9Vha`rwOzr5t` z`bFqrfF`CycAvs&y_ui-b%6oWNRiQl+ge9C-zjQ&-bPfrY2;dg7>?r=yZLsUYv=YU z8c|*HC_^O8kzxLu8%>SsF3heCyN#xTfhtr$5yQ2_E>O<%K>cy7>t&u*^Em?;BTh3b zUVWaYTXxVJ(@xqU+Y{RFjQb*(X`9P@#3>!aotd)!(68jo{_UCinOg=%-x0jaMI0PB zFMZwePcXxC?dkKHGSrBovC{(M41N*$)g5fw;IwJ6PIs*7RspwHKR@qH0dVDGLo+u* zu7v)&j57JNam-ID&Bo!YFK%X??~v_F20pT32H#LTO1}wYrywKOEQB`t-Dk(WZ%Wi> zfTKL4UyY01+eSjChbdc6$MJ7{Pk(&4mJ-!P=|3JVi=$(syt7;B!~1uZRQPegE>FHP z&r6{OBatWkpN38-Q*^LMXqm9jA~}tn^c+fg6Jl7^qDth)M?m^{b=y;KJs^(`<*F3! z1r9Mnn$Je7P`~|>PN7)4;6&~*53Q>(Pg@D;DPn~CNA6)1HyUA%yWY-t z--FLSYKZ-;JSgMgUC$Ssv+C3f!iGd< z(&#zT{lOiy*+vh`489U#=0=e{rVS{;yVbK%O3f+UwxDcMUhp(&FOLWlWN`t})m6VV~yX#FyY0h_H z^GXOHlc>!{d9|P{Y*V(#bDIIoW~}3!1);d7&}xbI3o)nTE`Wz}G*iRdH{?ayUv3Q* zF5Ljl>1sJyK{9vHnSYVs{#oZ*c}mB z;;FbU9q%=>B8g^R>1n@ABlVxwjDFNCO+?%K+qm_>F?vjtYC+N9A(m^#K z*P?lzu#{U9>v21Gt;w52G0?N1pK7*Zk&mw_w9`*&YK}#bRKXKG=?Siz5lVbXbAzer zjl|OGnHXxVe-dCxqXG)@rKScRE*Ti8+>j%FVQ)1@NJ;qf1 zXXq+#8n_dF^hkP+6~&QKtxCop_!EM@pAb z32&keFL#E4+#U&LbjA=LE|TEE)Dzr`jTJmx(mM8CCe)O=5m66z0U`pu{ zQh6_P@a1jcH%$kTvVqRkUrPag{Mruhd8X*!Q0Bc+c-lxFg9e z-q+f~N9ct&S!MoreqapPMxg>IF>T`WpkR>Trl%eMvfC{1KdAtU;F^eJOhrqr-s0TlR^qeD1l>v< zt?iTMf9gMdBDC!v=o?eD=OZEtX0XY?v0u%fKPggH7#*;+_P)y8|)8E56t?qYW|f_6y* zZePM>F4qt*zLAL+%6L*a9xNClz=^iGuPn$)-4%1>o*3jDEwWt}gBETXGkaQ;zH5=$ z-*aI?;^V=#>mP7$CJ{KiA4>9@DsE#J4n|@|Da8~3xlOa1V5!?6v`w4sb1`3hX5sn~ zt5!zhX^O@4z^Xn?GVmiS%lroPWUTEEE-JdH{`;qu|5d-jlzRJ5jH~A z(dWvZgEz$oA*Oc}Xt~IW2WMQZZpTUO9a?C)UFo~@T`Z(G{n_*$3!(0=-p_rVWpx6H zm&mDV+g4do8m-azdtLL~{xEL64IqO0r%ikLLr#+2qY97t_1k&gnS7>(!pz@I|M0I$ zmusJCC3}WO@ZkD|XB?)npo*?QCeWkNq2KRRG?HW$7jiS9hP$_u!kp?CPvC>eOPuFs z;SC!-D_nXJWXql3LM0esikHiRN;`?|L_frGTcba!7ewE@+*Bo6*5&@JyCTjR%p^%& zeJdXequ3A8J;rGk0;^=M1xiHS`q+5DYB}?xy0#vuH=859f%4kBu?ctBE3+@CRaUls zUkiVO6}JLui4Ay#O2h0Pa|NfZ!%ZIYw!T{9uRr>Q(PMs4Ux%nxOfl)56rYJ;=~%)s z8`~RMFm<(D%KYseE-a*=z2MPOPdD^G0;Hvgz-6wC7n9oj`Dp~ZruXGEv%~s3!pj?& zn9Vkw-$yioj}C=K2nFBRWirdSA7!4+e0E=P?9bI~#z89Zudib|J#@voX1WFbV7YWcZhjft!%id_Y1M}vv|rq04w zilzH_za1qlaX1NSynPVokSWV}D}RPpua?4vf*T7z1Ujw>2%!-m#BjQ5$Z}T5iZ5?nyuRUh5)nqet-^8yrbuvKnqvI4eAAsfn3xIn{54dXnNP z&RUm8WbBps>yh#(rRG1KI-!N<3c-Wza9_@j7V7701ZBRs!?}JZ8LXbbNORY&n5^y!co`_}OvSk!7b%Me9<`6l_$t}mdFXO>p zDlyU{p|y?M=sflvEB0X2egPm@x3kJu6mW+<#{skTL40z23QT>qL~svxmRD|5%`@=E zpB6$J{~ud#8P!%7c8j*f-HN*xiqqom6nA%bcXuf6#a-IsP9eCvJHdiWkV0@ydd?a5 z-uD~#Z^q8p$=Ykp{m7j2fgbt=0G;Vy!{C)yZhXO41t<^Hpw!~RJ{7tfa(Ka$i_COB zwK5>o!|fn5SP}CkR5ZwMTX9KFs)e$!Pi48hyy?full^~;?5RgN?JrK9U2t7RvuO?1~-BnS15=pg(}sooR5z`u^5D9Gf2 z1&yozx2Orj zwgvDpJo&1S&jTwo$;RYLSj#JP4}Q#FRfy#c8FE>Dz<-e{1C`;|l&oP)tHSUdtkvAP zM(gI$vlBWoxV~9D4bgz9hpFax(S0xi^z;EGL3Qrci1Eg^87tlQ8|NRYN=VgKO8DwW z{QkaaTI2XK5j#9GaU3-%R1n54c;In2cEZZRi?ZB_t#HL$hQL8ZgusDK60>ve-W zm*|OUC1p|mhTROUm$m)G(;U(NEhpi3!OhSQRD}Jn#z&qqrc-+Fr^`b4nkAfVZiU)@ zS(zUWl6rtbnkY?<&>Tg6$|nI_11vgl*s9a_E_`~k{7k|E&!WIq0#MEpZ=ekW{%f9I z$cIBhP53I892}{(kt1S$o&-^MT4aWHQH&LHx-@z=IJ0IGI{FJCtmg`>6r>N zeRcD+^iu^?*iis$x6u^THyujdUHf!I$^~V^FJQ6auO1iaANn~EM2kyA%73dEQVHMx zsyozz)%ekg!y$o5i&XHpX!6b3er;`Kivl})ksG#}F4-nsn&l^NOB$b@8W>r8PXDFg z6HhsCJll@Hlri!u4g#Rj0Qs=fN}hlvBy~P=Zc$RZO5;}b82WC;-H}}Gx)6C0CFNTX z0t>-ilYs-~5MB4VW-6CIlO1&wlR_r&aVv%G#iH^Rl0W)=^7CpoVL%5B{bp$*Q~ScV zs3mvk`Ed^Kw6jAi>Hx*o&b6Gw%qk_H-a>Q%Y;8a_MZ!xuNSe3KE&(*-Z*Cn^?$Qzs z7>MLps&1T~Z{3&+-x-1u{qOW<&~n|rw0X@qZ zm$ynd(r+C&M{tx0bTKw+WNH$Z1%&9(Ix1r&22rAid&*RhepE`bkp{m?+OpzbBZjt%UK!`l4`IGeIle;6SW~fU@$)wHP90kH*hK|v$ zm)9M1>PhG#Tq}vprc|=jw{nevw5jo5T@jjU%08932oBcv8p?@YO$ixgyWhxXhV&FF zor++7y7neMwW-m6E8vE-i||%%Y_rqWphMY{k}yx0cO}d(`Z4x-6F$N~Ju{0&l$R2` zu3B`DwOa9uk}JYfip0m7tGenE4kHhBpHFLJtK4N5pM24tJdPa+Pm!Ju^F51irGoBB zhd+J(ID4naAM;DWoa4v_7uk#G5x|jv#D4n=ZeNj5*H$%B zb?(L@jbsDEGSV6B8hG#s54B)TQFXN!}qKBEkTxI=o>-laojIJsFRs;DR-(gdjK9s#f z9_=8y>+}+T$NjR1VrIL0tHt zPImg3EaRGqwjCbZ>2)hi8Fb8Fki=ZMZvax4<~c?4OP?xrqI+NmlKzY`;04n{IQK<^ zdVb?fX-kE2tsgSR3Z5+>q}>=QQJ`AG3{5hAL0`w>1rJ-03$ovpVejUg4CxQ< z9R)$v-zrc~7qbjX-q=$#!RfEW3`QI>EHQsNg+~a$ESHg!XNNT#H9y(Q#P`khI=d{YI$z)M1bn#iSfD{y(q_z4cpSAr2oU%e z3-ay@7C{Ybib7mH3$f0|PJ{1lp zWU;w-yk$s2=i-OSuXtCARmg28(SPZ^XVgB-y|9?m{|G{^6QW%j#l7!SO6h2zx zI&IW2D_uYV)}DbOmi^p3*>lhaVEY#~53~yJD@(IZ5^ebV2;5IaYF|n@0HXaM5#pTe zkw({PqS+45ycJNuMqEP{4o`NcG*c?fphdCh{Yb(>JSEtIw;ZWoc}l-l>?s^I5sL`4 zx49fqg=*PvAznrL0%@~P1pyMiGyrAg%o*rEr`}=%@TqS0vywrB`jzPaSm1At&O&Iv zy|Vu>FSagFlI!jghB274dNfB5y(MLG=(_UaJG7a1RoaB6j$K#9pwl%^LOWW5U_9P4 zZ`MF+hi`Hmzca0Zx=dqTa z)+yb|@%|1C?aHrBr>isKt4s@|`em;XLOo6P{G@7UKS@d}IneB%-TW2&WF$yc?O~*{ z4=6*MU3_vl0?b_(xvzT)MW1|MR+4&=I;QYBBz>8}0^F3sSR{N?MK_esiP&earq-Yl zR9?Wo^^$VOqRfnXyeXm!1d;H49+Wm9=-j#MYrw4M<4UTpg478f3t;Ii7*YkXK&8QN zWwTldM9OV_f2L^>{^oh<&S>ou{(AZ*s?H+Ky~bgBtT7kB=Ow(@QBOtvgJ2cWNtG!f z`b9q@D3b({N|w5hw(5r3KpIMKRJYwA`vi^<%ov}4|92)CRTV0!y+!!6NB^vtwmwD1 zXGi%EGaEB^SnWhO?k7*W!V9iffS=Y=HVN&!FhlO7l54)X5(z>9E)R2k4jghAN`<=v zF!l-mbi#fw)$o%}rrDm$hePQ!t0e=6Dv+G;mfB<7b=kiji)dI=rt6dLjfI|qjDj50 z&jtbCjWDnED$KfcJ3|Y9N`2^wTcmvuUVs&6|QGP&$ylqDSpTDl|Lp=vMEmwIH{<@KOlT zp+(A2+{esx+|s9N@W&5|pf5nK!J7iDzxOP6->mFwmG$N%^a|0Z zs`+b!7Fn^na(kyihIB4Guh>DD5dSp)*2<~w9)vc8c?4|^46`a~xf3ku(l?TUC7zNW8o zlKr1<>He8Ay<-7X?q(VBKK_SEfDh*FhYM{z5VpX_k2ICc02qbjjSL8~~yDXbOv-%#7IGmS#Y+2|&%V1SPzs`;qDjaG#Rl61e7 zdbpci1#-HI0;>(zyyi2(wyYjTXeJEQFFx^DrCRLJ2FA9;B^$v{O*-+Itpg9(8bN1BR_;#mX{IYPS6Y?LkV80h$ITF) zG*?O7F=swCY40T^jgQX)?x@)!u{7_?`}_m$Tep#UfbXO$P0E= zB$it=mj?{JLmhY6W6_?gN#}cN2iLsVwgWve$3(8$XU8qxo1}PC3$p`ANHbynFls1w z;$rf572^H?8gI9*DFYx6I0|<4Z^|ia3%SJcfRo>?(gOcEbP|A&k2bNbQZ5AX(jjgb z$jaw+q|oWJ;k{1cwEG;;d#NJStbu$*y!3kcKlrX&TzFvB>}0GjPMt;zCe~56t>Q<* zc4B22A?+xqz6dv|MCKMEESk4d=Lm2k{*HWO@DrjprdVrVlE}CV-8KN8ue$OxN?*Nr2-vGFvRLgePI4Tj(Cs$j%-2R z;Fd_!OHk5(`@e`OvMTsT+)8gXWQ2{GU|7VS_`TbVlGK*{p5fl*hNQW2iOeO^r5=ht zHg;5p7>o48*!E~?|He6W%cb@A)|L>ANwj=8B=w7yy4mjeY+vJ(pqNkK? z83ubz(3V(_t)K#D$XgrPNEwyuq1L_N${UN~EdD|1Yt#&oQLH{Stc->*TdtQc`N9&A zl54ngqxF5pd(1UUd22oyk_GA#$?@{41LtEtLKahyOScz|tjtozFE|{s(DJ zas~Dv>ZmX=*o`Us@T%wHMqla5A!W%tHDHT>DS4cyOD%xdbq=`Wcm9Z2mdfUiISP_R z$SxttaJ%jRqWoWeNbt$7V6J&=8(IIF{zbs3U$V3h*=TPGhj}J-O?;o#Vp9B9-!u#m zp9ikTXk*C5JiSBZPoYV!=^JFGEHhQS+g-x=(JXJ$a4zvX&G+B+NN?j^UMN)Q*Bk<- zqW!G~JWNXKSmOQ&pThBR7y_9&kWW1?#ZHz20||z@kxO|(h*pd9tlhMvo*Up=l6l|xp)_F{yHq8OeXHx*U(%n4@JkH}{stZ#x2pFm4R4N$w& z8mUv>Lvn8J*FVMaZM}{x&pr_#1DR>hcT_s=Zzt&M|G9|BS&o5bI{!-Gumq=*J zyzX&}ATd%`bCRofJR64)xf)(q<)e0c0tm*iMGg3)i3V1r#96XQ^icml{`1LQo+T!UD z$4U4etR-#}qf9=Cyd<1o7Qf%`z_m`Md__i6V0`*0HQ?7zF&7g($#hsW2tvj>W=NP(%oHup2r7TGPSJ6(eA5tG^$iP$OLt(m)oUvs_w zxrebxBsJfU{kH-D#Km!s=X&t%lV;I(ff6yH2vm>6M?AGUafI2hDQ`FgMI;t|OqA)6 z&!X6*bcc@Z+-@X{`RDXs3BdRh8e`Th}JpZ_gmDSQ^vi7{$iRj(UNQh-9>eoJgt zaO*$va!=@p#eBomrMob9vJou1M zqC+#Z`p{nuIY#5X;~G!$I&f&vQ6eP)HKU;f$Kv~7WyTHmR~;TMzWGZ zmiC+kW5pocCX)nZjt*g!BK3<~?=W5Vw6D?5yd_2UslEyBv=(fHLnn6taLl5GOlkDi z`!@w*TLxD)qE$*#uhrLz+F{#-ok!KklX&bj7ynIklaCo(Sj*RTa3j;{g?|`?d+8X>h5o8ENQ=tCZFs9^`HKkFiJg$|D)z9pN$CDBO6gVo zX4ubqh{c)7;g)-+#=+qIB2I*I!4)(ga6MTR8SbdrSI^CSpb0O!WJ;*V3ui6BNtqR( zO~RiO`Ee?${Y%^t=!Kb3*e?a*DyT8-ONDo4v}!7@e~53Ztiy;>AQauBiFg@RZI);x zo?w^SDIJ79ygZ3*K21vz-;z;XZF4KO(FfD?x89&h9O%{fWRSti<9|?0ewabW}dfV`Y0V)WBP3Ini6p$ zAVgB|Cc-zrU-UV+-`S;$`HiKRz#W#BuNuNn6QR~e*?V?)Z3#m(!9Jl#DLemVsSADW zeanv`&WhxE(<|JN9Os1`LOLot36?q-j;BCcIPtOGg2ZNQuYMKLQm2MNq0pezSYDO{ zqBKtKFO($sc@sx5e{HfLb1KH@N+g_e7M3B6B6+bQu1K=^*fe|Bfvcv2q}of%qMb@K zxwEsWu|>6km;%NY2}WfYVhCs-q~RME+etpUu&@6Tdu>9s*C19aj?Y)y^=ZcZCXhn_ zE*ZK&DX{PZ3DR>&yg-Hl|o21b5wV*rBwl=y= z>`rrTP9(ITx~z?pqnfr1bHDE5N1fHl!tyC67jtF#;bs^gyTf4Ob|2eq*#0@f>h))R zLD*4D?#yQtirm1Hgc@WnC$8aUFsJDDxOkHe3hyThn?BjVPQ@PmBsZW3>v!mk36cHa z@KcuS z!1rR}@c_)fk~8o{@lwM8R9XL>dFC*aay?W_d(Rc5Dg1_{epLC1v7Ce$R8ri^T3iZ0 z9pE%aj@ftb`GP~rESE2=oNxA?_r|EmJa?BC)f=S2ZtaN(v_p>AS_c3cnz+N`2@Sh; z%pic?=o1Ao2tl!-q>C*z5l)$NLRh;$uIa-7Efx+R%bA@IAG2J{mjOkdb=}etziyGm zJ9(M`={r<0eXNgeDe4(~+lRVHBxWyaf$%k2CJtyntxKcky(}A79@Ph#K(`-CSrGf= z-i*tkePX*4Pq^=~8kKu15**(Jm&d}rbq0e<)@&&$H#&~~PWa7c++ia%>Rd^3ugS;) z`AeVP%O0>&)KfUX<>Wk!7IzjhyxVreX6nYis)%|=wm!y zpTYt#m!?}=j$tJ4IX3t4t|ZM;&zc`e0VvIez=F9mS4Di zdm}6Wi_h=E<@qtEyrSH#W2df)@nf@ss?0*j-#Po`N=oxy;D!s2Am6d5* zmg+FF%(v?gL`jB(C%`8|@~F$%)i6;qCTg^MBr)SloK+u)SM+33cEze2ARxVYarR^+ z|3=jIQGv9wFdP{me|WBY9d3PF{*GdEtbhOyEQ8|Rmpm8TiA#HXMMk1!43l?}!(XJo zH?IaU!Cy&<@}$cVM)O;^ne$>FV770;qx?t+9g||n@Xdw$)kXRD!1Ije;qy2MF{Pg- zHw%dGmXw1xl8@z-_Ru%ER<)6|p6z9;?5o)2p!uFg?<}<{eiW7)dABRG%p+YC#uG@iHRrT2TLR3x%Vb0jE+7+(CYg{JB|50fhsH9O2Can zoGjKm-(-j!??`Q|Jtnsg>unS)y;s*FkNL3qJDsdJk+xPP_zjL9DT)$iWfYHpJ`J@J zWr{Pk^)t1a)1o7xYAa)K{O0T~OgPrJRH%zPZS_iWLYZj<)@{Nn|W00RMg$Q=$@GK!62_ z3E758>D;OjMLT>4kHILzML9}JGnCu20?u*{H`cbm+9grh_Xs`cqf`l`_EW%IdT{e? zk7Pq7O%yv*M>Q==^JdsK$NAZV5`%F(kZ7ye8IJE#{^4Ti8(Bl6X?|sU2rT+^rvbBw zrLK8Td&|VrB;0MM{`(Nvl-v932}&(7%)C@{p$ql86_co{6|6t9F2v#Q9ol#k>&fSl zWb`p#1~VBg?-Ua!20jwTp0Ap8Pn33{>Nzp$2wgyD`UcofNM8BmeHSN^2fQpDXx9-`tIO9(U2%IY@DW!`LGK6U@{J3iq?Lw898BTVP}CZ76iY5zt0W!;9A+xX{Dbo*^fz7gNx`pCkH(kkiYRT1 zvAR3tv{G&xc)#(x{#-!a!}PJt5>F8l&3MIsLL8g6&f4iYRh48i@+UeBhulbgRw&Sg zuYs00>4L_O%+uzS-J#t!^JFBZT;Eq`hT@wjSUq8F@K!8|_e6>!I+u+TyzqU5dkz+m z69l;w*O>_qn#uStp_VQ@Jxm*@=9%X56ah#oL{kO+NRIR5C%}5aicWp4;H{8Xp$q5K z!=IUugJ!)EzhMN1*a>Hn+TXmxYd@D{t&Qr%zxiOUonMl-f_lZ#VHuqHhVNae@vB8(o=^=RzjmKGJa4+)%0tDUSuX%_%rh0?O3 zd^{or^jqC|7qr54Cy^rrdI^?kv(}gEoe{x^4J%6=UuH+azyu+T-j}x|g|5hg2pf`! z)A^awrenMI)TPd_mmP!?3UWp?{>IL+kKvWXr*`*aAVY)~_EwnU-ufn@;_@=O`uSYn zn=GLN+VKkW0-bA&sj!VIsIPyV3&HF4do$aP zSp7`4N9`ELC3D%qOT!>S9uNIE)=uX>J_=xDWDu@D=L<9`09MR}%r6r&EOW^}JA|<& zP!f9ztuG9XDqC{zM{__M5S_EWef%YwAW0a>KhyaYCHeF$9O{^L$98_Ru$NcN z`Are+E)nDDM-+#qpYhc0A|9$42nDX4x6V{8I>K{Y+zvNNu(PiCJ(B_hicPY>aa8hxm0KTUr;S9bRg$k{ttd%EpWUp!OukKC zRBNR`re4hOe!Kg6{B3yM)m+YzABIQTgUIuk2k$e|Vtv8CZzotNBDD@6q@RK5$7!<@ zUFg+tW92Xm?{kONzw2NT5|qx%LL@%Bl&_^IHl?z&4yCR&Q=B-B^Fe$sLEQM}Yzfn> zRfcRkPD>nYe;IUeG|#7uyYWR!+G-}zPx*P_%lRRqxVa2a^APZ@<% z*>KZ%I5zfwZ#wY!L7O#t_}#Fax$q$1zHMbSq+?h&+8qPxn+l=01}?*2*+F_n%n8T1 zX);tz?ZmZSE%D5*Ae%A5QJpz1+VbtJnW4_LxW^K>QJ;soezq03-ui;V@#1+eIF(+yc>VtpWtw-~R z#dGXZBHA#vy9QBgvYSFA@|?;wt<&(P_VYCuriwls8pjxXAoJc@$)x8{NGOyt#SYXv z8F5N;td0D$?0eP|*2!&DOtlm7T1>+i5ft17JB70IboDfpSt;t!U{{fu?OFcCrfS*b zA>gApw`idBRw|(dfzAhPuD)FK1uN>l&AWZHaS&#gDo3zxDuRNP zr#|{&&L|l&=@MLakn3X8=x4x1E<(%x(MN}f-cK{bzG3jwiO}ZEvTYFQ%Cpii*wT!C zU?_Pn@wEHu8j!RyP~U$b{pb!0AJuH9=g_?|X*xB`wPY4TCi)4OB2jbWHU4$Jy`U;N2BeX=rSU=5!Kim|La4!~2PE(-rqKk(;995{Kbf?1poylRo_F22hfKug~ z+JF{EhE~gu+WIrE;e8f066$+2=4axrm0iBSEIIR<^1%JQ%#n6gTU61v)HYAMn`zI2 zr8d0;O1wiU3=FXk;Ne^pY|m@pd1lt(THTfyNU*2`N*gAb%HWTc z775Mmw|8s5d8(Jf(7`z34zxlix+p%nVpE|JHjVyq^PN0br$)Nk3&Z4!>wJuFjAk%i z`Zz6Ng;Mu$N3tUcBzD?(O4!S>r4a&*`qfT64XGmUHY^$Mlpgeqaxa$6Qr5q-dta;| z{0nU#71>nkRg*7ymvK0*_`Uj;g@b`+eBYR@+Unv*Y$-2#Y1l#Y*SYy# z>20on3Ihy!4Qi(TXEQ;IkxAR|GJnk)JKF)W=}=+?(UW$bxLmv}&d#nV2KvONq5_wh!f_L^eN^rB(2s5)a;x6Uyeh3oD z01+&Kzg>CJ@bue+DJ&XAClf(jg}t20fALj6K!?%dG>z4lBeDwK23O-XQR!(Rc$~Caq=@-> z_FHb2V&jsh1YE}?YT1&*xR9IZbSD_S|1#^#$IE@(wLztG=uJ}HR;EPclL!gYep0Bc z8ZI>ZE}djaX?JU{$1mD9<|ZsfPNLsTJi$zKCB_1MULjAV|q)httu03UK1I?bt4mjbba zlS!?jAALE$V!FWn^IFSf^h0*naV-LnBifqI+{`<@TjOM>~9 zU5ku*dKo{emzS?uScz$7IWXMsR|WuUVTLU%B|+ca#yht@#OoZsR+cet`u33~pl zhC}Jyxgwh!29qP37Mkni#%8$BXGu&EBkL068XeHcs$v@#pOWdSevv#wmSgG<3%Dba z-w`UY2^{YhZ_0=wY1#B#B`NUe#U|zg4~szqDibKuTt_*W)&8U|up+t$+fl_t+S>g{EC}pn-$Haa}4T z%_oR8c;xBnodnP;;m1&1iJs-_HB&Xq#g{e?a0f`NC(gpMa{#Z}vIi^0$;-#{-D?7C zr9%p20uhDUAmV#huouJi%&X-hr?VIESF2?P-z9YIIjL-k_qru9&n1aQTes(2T$q)A z#U)+z$flG|ePBg}zvbTIdI?Xk%feqFzd#)~rVZ}&3eA*NX$W!3%v{#kcV)z}O=pq>T`cSxl>u^_)O725E-6nKNw}yMZU^V|t{mU1x&EahIpa4fAYIe0Y z`Z4P~3G(XzFxHYZExOq0HMU=8Ja`IY8pt)`5?5P)bJ-uY!cv>QC7_iiWCPIBntZCO zqKsFpQ@bG>?jq77ngwnmurtkv_nMV2POaq292Pr`=#)5{NJQP1bfW` zK7^PH?WoFYRA2jk8N# z*>4L20Uite0-6~)w9?Nx?120<2*B2{I&(h3$bif}xtjKutmseO!H36LXZKE7S3kc^ zhd(5U^Wx#Ugl%b88prUfboKj~u*}+q(ZtDK(nO^ zHRS%eat{(zR8dg<0ZS&{hdvIVfrZ;;mowRKdA)P5uFHo0e<q4TpAerHjK)C99ckc00H0Rt0fb$hW7GApd}5*+&+TABMU{uEP>Zj&ccDcrdx! z!0Duk*8g1H62zJXF|njReU|(d3EpsCmV+3)<#Gw^Qu$y6Ua|KrlMg*fJh8EMW0W-9 zxl}KW_~io<-4M^6JD{lf$2b;^bY;YkmA@((V)$C;advf@Q(DU{HasAp`kKdzVN45d z_W0R>@R&G6IUKI-TEr)22_Oz1 z(f`2^d67k9N0D2og49jBgdo1;KwwNBt;_pF%Abd&L6uuBiFEp!sZSGXCI8#dG=b3R z(7$<5iSr(j*r#%vux&i}z1`)Zh9BzcA=MZ1YPU6YL{z~m*j1x&&6<=Y? z@5oNyT6{8Ki>{kVF?7GOhn_u`gQtFd_}LC(n#8FUz9E4;r$$@3b3fpldepO$i-@~G zJxX{SR^-MpFGoEhc@W}kpOaPGE8*Li7#MDW_C3`md9t^r77?F4<5W3ILEQBV($5%@ zkC~j9J`{xt?c)*)tL8U9z|SIwdhzt_09@fmcNC;t%Q~~1oR)_QgxSeFEDtORFAT9< zIEHpO6Y&#nXxD2ki(F3rM)hE-X^OP1Qjx>lAc57RX?J*2+q1`2ikGnFHn?;I zRePXqc%|>Cc8-$GS8&IDhP}ULrxbPI55-9ax3fJt-dt{(xk!ykBcGtLA^D*}IfQji z6-Z3)m~&E! z544l2?gy*@#vgw^`y`BI=5X;(47tPFnU236u%hq})bg_-QkQu*9=v=@hzIZtBw#cg z#+J#Kc3ENXLJM7eBPCMCU{O5n+d!F`;dPBqlX8IF#-U*T9{2~-S`c7Mw^Rm2Ytm0g ziT5Z=#{2Od7q;B-JBJynH41k1+)1Ke*pV&aEE?KCv> z$jXvWqm7BUviOLT^mkXy@>bz%5Pr;E6Z}` zSd7GvZzAzuN6@8c>rIdqD|ItD-0I)-FTV)l2ht|Y zFK5T19i-_0`m;(WGLu`^P1?lN7_saEakQn{`YC;RDM!?s=F0OR~C{$~^W_in9H0blWG>|iVej>RK1E*Lmd|>V#(FjqaZZt1b0S|G4##lRpnQhch z{|oPIYd-Gk4}-^t3%>^5p-$nfkA8R_zydxu#`$4$V=@Wg?+HZvc^%jeeUGfo>2O{u znDb=Fgl;mz!djTsh&;FwIqSXq1TG{k?@?<&oc&MgFY4>7ldnf^{nPNrAi-BX3vCwb ztq~qJR9VX{^^QrAN`%NM6XVgpU6Z*U3%uQ|jqciw>hBULEAYzE6>DtF0mGaRU?Y53 zGWyf2A5=F!m&RErq9A}4U|p{vkf$T_exGCEk}NBrwl>{+yOhKzo%tjbnWO(~L+7sZ zvXhvpO?N;dIPo$}ksQ)494Ps7nG?oI1V>zT>?zPEn?&P2~(U-gd*+3rIzT36(GNGE>BoUTV{BIWJ%*JXUzNxdtaR~H&a&wzb2cS%NTktQ?dEK|u*dy+y;z~J&cnk78*`kZy;nBrb|wRb zy;KOQi%zH__WI;qz_v})zTgiB#!ac>q(Q6Eb8FqCFW)Xu0@|_-y_E*j4@t#_2 zLLRPWS-7*DZu$QVtVoDuBtqgjKnp}sTuwzFzS8iv&I(ysaD>3FInhu*$SIl0ukTX* z7IdD5)8|}!l@f(gl3OS}CcH#e{!vZZ#Gy)>>1DE&7p1^}2Upm)nr`De2w)&!$nu-A z$MX^L$KmW4z1J3UtWeR5)Gr=sLYC%tIj8kfOVYGDV5*x3AEDC%W)@YhL3$xg2fxRD zL@sX5Skt^ip&s4`7gKznt$p*nb!h&G>iqP_I35i!R<@CHS}=)D3<4ZuAjAt6Az1c1 zBIX1;{L15fv0{70c_o_K3{Smf-X4V!z}!Y4HRUr(@jS>N5YEDZ?k8x9R`LAtrux3s z%<-!c$M2g*#e?j!;xA-9+;q!R4wT^i&Ur7K$^n>HX8iSl$Dz%F5CXd0oyX}^!#~;6 z_{%wA8O`h?tQ}})|0SgOzT0N11d`*}gm~86^ZXtjj(9F?p!65g7o)(ukggLs=JlVo zVsMgdgY2xlS~?$U^k3KXXdw6SlrMeB^FMj_MOfDg(-@KhL}Ko-H$vqC*ye31M5idV z?j-W=$#xam@dD{CCE;pi4eVd6j{w0P<`@_0E_GJhQ!yQdwSMS+F|mjW8>e@L3ima` zUdRx6|4yaT(3lSMD5O`Y)va})($4GPe>%NxNs8h&3f|%_Tt!Bk$9fqOyvKhj4q7HD~ zi@|HAbFU$iA3{VZHuQ``w2)gtR6cQKFpeO-7V<5P4Q>$N*_dF9F3uus_ae~(Xy;4c zw2edWkmvNKPT_U!iP*?5F}GpYCU>`;kO*^|3n&cwY+ZXVM+^2M#mBlJhpig+`T)`Q z_m5hN__y@%e^V6nG(P2bygXjzMgQm3Cq;pY2>b7cVDcdXOpb&(5W@_4NM2(p%>Vbt zh<*QgSkxOP%9ii{UHyN*gb3Sb@ZaaDhG|5?-LmD=_>^C^C|kxV$xwFPg7X)KN!LjX({>B!?54B?Qy@)>x`TX+_BViUkkyCFRb}YL* zQ)rTYw_5p%F<8_Twv1?Z7*4@A{MhLmy6kx;;7N`2f)UL{{2A^}zVU;Z%27W?7~{2} zpqY<=TCY|0$P?p7y1Ar09P%SuCfdSUei4N0KvNuVfIym)EhqrOX6~q7$d(=oayy<^rHleCzcmBXhigpU_*P*hG*Eu z=b4w(0S!U9;h8UfU*5Odea%ldGWslfQ2HEPGqw$!5QPORC0~56!k_{#|5DyG|HGRf zM)djyEhpLW%1o;lc5Mc)slrQLmS~~-{3CLasArMcaZ+Oradk*jc-;u74@DKe>1S;k z!ObP!UD-p17 zoVSBedY>&gK`6^8NHt~ue(~X)Y9Ki{10-nadN@5u*{qB+@W_yi+hup%<%gNFOr)Im zy=R-ZGm;9d^OQ+k1wbxMh~B!(>)ksn^3J{m_aK6joPmtuj#MzvDdX#ZOU_z%utumw zJ9EQFu6QIkkv^GjC0`D4K{w|<2}sjUxl1!*%|4;vcPiw^aej_vH?)xZ3LTGn2Zq+h z8c;M8^_R=3F;zh?o@ll|eg>uJytqpzD^A!wl1ixbTaZ`I|G}pJd*WVG z{)@Hxmz3ZR^r6DKcykw6vF`*!s*KuQ5-AYvZxN~GQEF5;L6+eK9eUSY1$oOWBfTS{ z9`R6HlpN{McG%GE$kV1?{t$<9!U5F-0B){rcv zlI++B$d+L&F?NdVjD2UUGnnn2`>y-YbsJpj2zP_9F5LRjCyb=-d&J#ZAs}4X}z;=X!!9>;!tV! zcv#<}<*BDBT%9*LUcZnOuX%A65$ijUh1rAz;Dg^Tt7_r1Zv9-Y;}ncteOtbg?0n*; z9bxbNsnl^H?F6(|0wjLe=4Vnn5&3e##3SK%Nqk!4&l)Q4mIN3gAw}Y0>EShcK4ynT zt+9`lipeD??&pf*n|hzNNj4ex+J+wQilDb3dHuztKAU{DsfQVi9P(Fi#iR62`PfC_oLj?m}!m=JW&vV*o_x(QIIyaI~s)gUzmRleK#%1DzL zTl2jn4v<+^1x?TYY#ugTe|ktadK7|qs#9bCL9MOeYLykgYk?c%2nqSi>bp^)-QObT zGy6SVJ`kXm7TS#tEbNo*5%nU)AD=dfHd%#8GRTdo@}*(~ZO+tN%bB?F{;jY6k;3eM znm`=ET_F8d)Js7b>#)e{Iixx-HbDuR9aYDf;s+Z_c0IS2mOOH@Fgb;3j=>In*tA|y zV;SDsbw=`{K#5wKXw}i=B~-10K5uz1Xe+!r18gG_%_5H#Wd}2f*KeDpfw*&vU;@axcqN?YU=Ca zV<5lPxd5(^Gmz<1!WId)bFL|7Mzr3cWhr=bw$vfe4`MITjb(l1T4!_3q0*!wLYCmj zKop>VIlIIc@@h0Mis~->w`KN3p4Ojf(0^ucQEMO$y$#-Sm@50%vW9g!=_Z@Ey(zIz zSml3q@bL)b{ z-4mKtRp_!M!J_WLfDsr_vWJ&zQFZy3i^y4yo1+2IN!<7ScVauP}y?R`kB0BK+^+7%oa@z!uhzYEDhCTaANYMexJeCT&2T?z}!@qZ3b>bwr}EXeZ?aSu*0%-UQHRrg!gB+ovbX=Ub@ER0r(^TQ9yf5!%ZfDH!$QPSry=ckz zvsQTZ3|Gw!m5A#TT{L#ZaC3zoSW-S7D=6i@^Wn|e4mcoIJ&6CX?W8qVf9nkB64!B# z*3I&ik~|(?G;BtvoXtNbj_aBd`Y-!Z%umEz_Ydd z06|*t4S#mNYjam606pq=;kxl>*o>ZS3*3s3{6J@7ovxi?eIog&ac!@*Iusq!rbPu> zGqyn6GAXp;Xd&+I;$y*NbSM3(7Te(ihJCs_Jn18Z&3jZM$5E@y8{6FRfi-d^=LC-n zz2au}5)I@BYcqOb#XzK69l8*m-GU*(n4#^|x?-2S(a{Q7ss;G=L5t zt8G;Lp!yVt@e2+S2Y$XwMfQ5!7B3a2IJ6l%E0F*(u;$azaWB}+Keayw5qemQh1SwLPi z4uZYvH|<$O#^48Vge63uzEjcHb88NVzAiYBGxz;zYRWCAPn8zWzMB`mmg(gsPMx-H zn6p$4&N{UFh4mBl0$)pG9pq^Bo*ZltmJ0Z&dHnRu?Umb!=4?uZ@l4SROc(R(B2F1G zSK43JGhP<-cQWDK;D0vE@Bvdw%O>$oKsGl5=Wz2NN_FA;W9Fv|`g-wIEA9Hrr@u1J z2a501xAbS;(AcZL#&)m0s)vttq35GNzrwZrFN48PGLtt4%6tVv@eiEwmijFMqeUTx zs2ckbFP9p3b&Bmx*xEAqQhfKZ^H2k#Ix8`B&xvbqFKfI@NSiU*zRmP#)76C6SUnj@ z#{*i47098qR#0m^?GrJ1Ueb@Ic*cD}z;@H?D3+YndJ$~@epaP?^Zw^J=QmB*){cXR zjI?5704FRzT`t|d^V4#~l7;3c;U(@j4stq>*B(G*6z=^E%#)98hKbQvtg)~;RRGM5 z_3j+zLVB%q7|kk+(SOXlU-Up zK~7`LxA_IQH6CvDwgHgbvlkJj#xAZF!l0Vz;5}n_?rQ)%(|`N{+-nV)r@Gp#M3hRo zeC5O@dc#7L2B}Bkz=^Z#kl)Sm@GVl%V5}~?)HCmc&xi8Erqi>P z(Mc39JzFm0bikfVE^&Ve@+^in45P>T;r1XJH%K}_{P+Axg$!o z6K^qvNP|Sx^pI;Ov&wh%fXllUtO@V)>pDlGi|8GkUj)0Wz|%qn-G)A5RGXc`Cz`h= z=gL&SzI~G6;Gujpz*IAr=>ewd&&5i>Ff(}%;%mvP09(zlj>gz>n>t3o>1N$g$(B*o zcofwO;E8HJjJW>UgXaSy{!c#>9Z$9gnRN-^GMIt+F<-I{C4NZXu1-CC)$J--b6*XLzGn3fzq3Rr+Q2kdeibmSOU8Nwdf>Gx){ISr9yc zUBU{v_}qZefhgdS3*&l-*tyoyVoJCGmW{iI|dxC%^NGPf74LUcXW5YAQXJ{)IWRXiKH6S8d^ zA=vbsv7E;#{|AxR>0W(!_?*7aqr2*CVPh)ZJ2jay@dIudlwuj?(tw4p+Q5O{k6PRM zL-iY>tBzdvGspS)5!zB_p_{uJtI%uxVXPygTo>9F3p=-cSTi&9KRTk_?!8vH`i40$ z<8^rEE$NMTO0G5g^A^_=DSN#T;xOw>u0T@kAfSw&;@Qi#fiP)Y!(9|w&T3SZZRLwf zxzBjv^P7bi1XmT=BKh&PIg76972l~|hi_&WCG)(y2!#aLhzfiwV^vDxIE5s2_(gwH zX5!r$|583sLB+R&z1H!b_3OmJ6J>TOi9o-pkDZlE~kK|B8AN*A? z-=2xZ(8*9!+6j|8%H=-LTQUK@d$Nk*4V}7#t<`!i!~0y_ue9aF+%qAN-Lsv9_bdO2 z_nTgffI_wNaJe*2sY_{h?{PT|tO)KF27i!iq>3Xa0TvU;d%)f5^-6*^L79olr*d9t zi8m+A2Yy6lm!LQcyWMuzDZTh6Elsq8c2CpIj`9rFMUTIcwjWR;iZ75sUH{U}Z8uRW zVnYo6i8B{Q$T$Ma1=+%yO!eI6kJw#b7*gSQ;a)`-dLiKsw4gMJhxS*fT(&afMZR~+ zp@SULE^ps#zDkNlZ(e@ew?hpGm#CbI7&+{4axF!ZF~19++%i^1+bWzc3QUTgY%ZA_ zue)Cm08(49su}hFKyes%nk}sn5thz0m^Q<_%XTM&@o{hYBY(>vw6mnrr+(l;ICqW% zkJ=#loR;bp^WxXKCx-_^9ks-*=X@Q{#lX(XH|9E zT3!sWBsV?^Fv6m<;B zaVWI!j9_FVVySFPiodGk?N*x*sPR+9oD=WJDAG3`9~HRyYWXV9Pw&A-EVgyb1L#@J z1i*965Zjb@PH^b*xi=FGuI zRY{>2R_3C&Np2DV=>VM$t%Y7aGeEkx5Y|PKAR7Z(Y#cZ(eUab=m%0EjRpLANozb3U zk-glmJCHkr7+9fR35Qp8D+NC8tVU+-%quI0;!JXFMlb;hNHuFY! z573x+T0LH`Kl?nHMSIzqzU8X>IFf9kJ45zau&`x~vnBqg)gva6CL@a)a+F5stPz z)3jS@@-4yR!UbZpKx|ID76qRwp4i~oAnHAMRsBm^_^={-r|mMWQ}t}w^MD5wrt;k; zx-3WR-f}DRm7AK>CkgEWGa@_mElPCso|ErP<#b!c6gab*Z%%M88j}KlCKQ#$tDW8A zEF%DzweLfU^QoNl>0fJZBDl=dg}L~;BLZBbnG@_+-=%-EOEYq^mnCObyv`dyDOT6# zpF~-ga#>HRarY)SzY2}BsiWQ=eb4SS2Z{9iQn4#N={(}L2E1{axUbajJR3gI>K*u9 z@Jf1L&&i>Ya2Hi0H$A$Q#1)!)<_x3Y^ZvQ2Qq4?fVLO}d#3YN*HfL><`}KkbcBJyH zgB-*?@7&ArE6ajPw!9m+&A-cbzKejA1f{nG+SAeSa@{H9xC$E7Fb<}8STJOw{U(`` zsc1-T>GsXW$#}}x9zph-bMa9mkYIFI<(T<-e--NF&n?@m;heoqu4&Sh>tI`oJv}M+ zGy!i3noP-$)H=%v>SiiNgL)Ab5p2coCtR?=0&f=yAU7V@J< zDx7(!MzD9jBK%0n?QLmUQPTwHiFjv$+B;#_CggQKR$^c9CTLwtnGJ;n1v+KZ8JS0v z(k=>nKXgX;>C^RJe6XD7$V)LM;F|45&ye@fKJfaKYoO?M#g&;nm`7S$p;lm}xg6pH z`DER2sD1Q+>Pm!ZRqrkL%T(q~ZTb4AA0TGb>1m5I`0{Kq!ZH@sC6D6;Dq)U|6_XFL zRUa8p)lTLKfI?h(2NJ*XF>~n|FXjha*>EVq#{0wM`bV>UMRL}o#$cHHJA0=lzALv4 zQsxXz)N3q!F9(9Yho12!_{tC|-RFb2L@Rss>}5xT>sugsFj528I}~RIUYE*uf$~UV z$*H-5aO2jPO!P!ajq9U~H9l;#aw93>nJfAUdk)?8nAI^bw%dWCwGn)_QQXh(<<5KF zA@|@@sMoZVz45thnL|f=GVeB7c~C!&#TV1s14(9QPgUB<+FcS8x_S? zLsWwafw7oN55OTBeo&I_-Mz#n#G}}RzLrD5-7}?fHZ-gPzCDxA4}DVhFVImAc*29)Cuk`Ns4SbDN0HChT)2~w8ovX&HS zaRcpVoOYTMyP6Q#^0yoGIt2qD%w4I2;4wQ5)tFiEyXzUjpLe-qU&#&BD+}!9l2U9` zzfx?|ULt!C$D4_J>!|63=C9mX?DEA!Av8HewI6>zm!3-+S~<$o+=-r zd!IfAe9}mN5gyA+-f$ju<@rrJ0aLpc1-eI>Ra|O!XXCAReG;_pe>{2FMe15(zVpt} ztEiWzaipAaV3w@P>Y#*yK$KQK$e_PR@_FF599tSaZ&03^hDCO(i{`kMHmnE6Dn>`w z)4sgK+UwH4k1pnjoa}@#Y3Na!&z<#7*5RVv%|1)_VQ@{hrhI;^^`^KGU5$C7^u5Rl zP1^4`r1#ILI{u*`lV- z(IP6^V}EJbLaQ zylwQPD@`K#4XRc`yrmZ%B8zraq>I(&q2=8^#B& zO)D{GtpvrZ&g70Nd_oo!9$SP=2@P%e+!}U9ifZzXiutML9yaFsLP>i$-IJ@l9(b9d zsGa*I;`T#caebB_(j06_)?BO$`eUE=O~lvGq%ywa_;50wVWVt4CoizQdtd>ek3hnm zsUngSUvS(xl|yB}3JAxf>O>$qHeQ-5-dt^&SGoafyR zeeIPQT9mFxEzn~z%F|FcH z;5VP?SVH!1qUt1gdBYB9{$M@ufCOKE>i;Y_DMs111mjZG z_E>diGr@`qeK_zvPXe1$8?Tx@(;)s=*rnS~W#vOy0kF4YH-$?C+3CHf+W2*Q>5>J{ zh4~B*Qv4-%qvDJtpg9y1VD@PD)E6qjxM&RpBBNa}tJAJg-)?z{cS^PFl!%6-;1z3$ zvY7g4`yn;P)`z1hLDsbUS9eR?U6VGwW7j02kVdxqpOsYSg#?Z_rsqMFtIdzY-4<@I z1#|cXz-^Not?R6=4q$%9jkSbPqr@dp9~>ydAnCr-s~3P;(kCGi)S#5?W<&}AghEA& z*m!uZPMV;ij26eOBF9ib2uIoQR}bCtCgCT`3FR$S1v?%CZ8620@_NcJ_WNd(#O>{q;NiCdmgikP!Q{{$Leu`K5*s1|OiC0|egaUVly ztFpEqV>(B@o{5=*=!p+2Udj8V6@K_JX5|5}`J7tXoVKOA(G%BnsD}ZHS5@4zgYZ(jD5`P2 znO*2)ZQ8F_Ny0q8K#lg}z}xrQ8)b<*pG;%eeIE`rD2;^aBuro0n`G^)yjv%8iLtAK zO~@!-o%XQ3*47~G;adtt_D_xues*9*s_3_kXDt%#@BJ8VN8Z?LodIBFXn>iK^B$+c8xVhc}EzWY z?Po9vC;G=27d{Y^u3*ERbJ}IzuruSaeoS(eQ{I1>Jfp`FpurD}Pa1%4m`K-J7^FQP zTt93$appn=m)HdYgf7(=k-QZ>T)t!u^+cR@>VJ5~jph6mBuM&aq;~IE`F-JH4~HXAXI%8dijONcRBm_h zthR3E0NRrGx=~E}s7jaC#A=%Fsjb#f=FT-a=EHFGm;g#Ouln=hixU6M%Pg;J{)#O1 zx}mp7B4y++=Po_B;X!anbHz%5|Gxj1Ev8S=>%0T?MYBsx-q@I>5C?4X>|H_(Ntz&4 zM(t^UfRHw4m;tF0{qd8m0Xms|;)t63alml0S?jrAbZf2!;uJhVjZ@;HplsT+v5IG% zU(+Q6N?en?;7Km}m2Xu)t&_yZxX<8TF9Q$THHlq_pJNn;>eBcICELCSRM%A0+0Yr4=dB>v=F-QIUvSd7rIE~+*4L`22=*b1Fs;fasZ zOA~uTU=KdnRhF`FT*=FdG~30+nb{r;16xvkVRa(k_Sb)y+3q5Vz1ZDvaH~+38jnQG zMKlTnbb4n;iT+1;DvDFH*Y zppl1WHlgF`q`yN1tWC}aLOnwNi(=c1*Gj^`#p|lz4Jk$=$3r%Or*ln?CWW;5o_=B2 zBS5*USBTq=NtDD!%l$0+1mb{J85$G?2%Yk`UV86!rvvw-i$S5|M1y{BYG2OcZ}8!ZZPC-3OvOKeSqMR{$hU!l~I&X)!Oy^1alK7RToE zUXNKVH-h1*wXKJ7A z9zU@2Yb}rDJAoiK&hu|6lS2EtKn5KK>9;(T#B19gnhb<@s6C%!e?)&r`g;Du9}SIt zWm*>!t=!e^udICBiZ+@29ta)wU93HmbDJ%>j+HB@Z{lP4+l*WHqmn-+6u(}1l;xaO zlP2S0isF4So9xhH?Gv&?$KoDT%U2a=kSbW07WI`zE+PK?k?yiWpvM_8(ZaOQ21Ar; zFb6%|V27@yx3|JJvxwLmEvA>}7~AthEZ!?di~EkU^j7#?2VUu-<&sibEbmqMj7IF; z%#pr!uJi77fn@$rdX`wxN7a7`3^#k@R&QRB+Z|Z7K37uB&J|YkSQi>tP4!46SVF;k z=c#a%NWr_x*{%HnN_*Zhx5UQ2<;Gfy6~SR7>8^6x*(=Ij=$80wI0Gt^_p{hgLOL={47EHtSqSUl_ZgQF zcVDo|bw;NqK*co1;%C|(y1Di6Np`3P#P`Ty+q{&OKJ@{Gbe4Kp)K*fP5_uu&1o$|> z*>Y9tr!-KI^K9yp=2PHEgqaFe3J7M-h(R(jTVr1nK?ToxVThn9X0VkWMk}Eb{&bWE z;vb%1Me10zU-IM27a{#yhp~yhBqU}ZqXpJ}G}ISgbx5+S%Gd$TYG@zMAo+xi8l)4c zjh|_F6tEK zzaKb0gPVo=sMzvyg6a5JB`xZp9II6I1Ls_yKN>3Y&qg_Z3$16*lq-z$s&`g~QDr!k zbC?W64R^8bXMcupyriBXx{%o*fTN(kBw;nfTsVIF2qLUl0$tXW;=K z2*`ov4Zvv__Qdx-@jlDRKvOFr509?h#1#-|oD>70bP!C6M+|z=D}dmS;AjE}76WZ#bimMb8#uMGLJWE<1!F};slmuYiswL8 z=$tp~J$E@gntzD0Y)tQb#aKrgAeHIf7trn;(1pREsennz7mRV(O#?|gH5 zNHbZY;c|(}(b__q>VgrtCvgnD#!80eWA#N>^t+rP0n5AwyuV*oWeXi<+d!&3>2id^ zlcJ`4*jd*2jXZY=hS&2YrA1E1TJ+s!H7f0@ep8ocUfN$f>~8dAM-8Ho4Yq ze7clzSO!D$QFZqfBPTJqXBo4)G~| z&6`Fq#~}K_hx>aW(>9MQsE;q#>+toYOg$b!GVmeXPj0T*bD4|P1@=(ya0j?7Xwm}LHzR$~u~h!whib~q1i9?8oid~tZcrWaTB zpA@pJYBj}5Kwg2&R8zVqOd8Lzn`x;1ny5~{w{cFyy2CjmqV3e+w!T% z?I#yjB<9|9aEvMswK=*Vu1+GA&}`9`;kY6QHK2ndFMv<~d{qU9qqlUFJsE<^>1)+) zs_GRtxO?MnZS^U1^M~-p1B4S(z&!~9gKJEIVEcrK_=YSBj{vcjE-~Z4A!LmUhlyuL zY$mw=$urj+k49(%SU?%o*^vh&oS<&tpk3PXr!ta%`;?Ph(0Q8ZZ|@4<124Gt+RI4}xq6A8@e&sf z2ak4v;LfEZcP+g$!987GUkyG`P8Goa4$qKJo;YtAH|NyGk-Q`Kzl2wVn|D>uI#Y4F z^@{v>*45n5R?)J)WZRlUT~*V(!?ATzfEpCSB+#M3CtBsASLv16`emyplbvNeCtI+d zjsXr%n%cY&H;zmp-)G`8e{2@1JIG4iLMut9kP24gF%vy7#R70LEW@fi3 zMvj9m)|Rzq^H_WeN03r>eKsmg`udTmEYgeG<;>)cef@XZTTE{S!k_Fl6=S^WI*0Zo zH5hB#Eqpx7lQ1g9@1I%lY&@jH-eVB$9rv%SA)#7GMYIVza<5f^LwI7j0264kNxZ zOx$;zIzy=)cF14tWwl2yQ!yy=ztHqE=Pj3U{(M}ar{b^eMt+xTGsva{5h-xhB9dFF zdD54C@LjgWC<#7;Uow|y3{SKTHRj`6%f2hpADvJk8}9u9{(}C_&O(~&MLmFN%MB)a zJH^y&&ashv6+Un2aP8|Q-##>L{rE`g4{@&E(R%c9VDpK0B<05an3u1j9BvMezZu9i zztkRf`YNLd%H=q;eK0zV+S4l00_Ax9%lL(Aej`wHIMg$S;9pWPo)9{pxd(|LuC zZft-C%2~bEc-{WRyvl|YMpKO4V z$x^LaiZzw)l|z5|WOOK7&t94?FaXq((07pai^cj>y1H+~wU3+`C%56-d=Jz>hxAn( zz${k=sp@z=`Z*>8{0DD*zYiuOg+cCI_p}n5T#m%lea1M~{*^dWNmvt>vr4wl>Z{1C zL!X+zbTe$QhA=+ynQvi*Kfc2E#>6AJ0)q*vw}B11DT$+a$-_{eY(x4(jo5)JMLe6u zY92{DI?7+W^j;%zT!B_kjCa+%+VOWyR@95_rL9dt?u&#|-uf0TSEN!a}8 zjq|fbzvasq{gi~!5jPE|BJA)4po`=>L3c%0@EbexKl~Nfh;KdA=u$?WK62deH?l6AsMP4-W=_IS8heM zz^z|I1)GeUEN|C!*WHx|z^OKlDkcwYcK5F3Tq9Bw&+O?mXHE*E`dAc?K(G#Y4;&0- zYa(bR=`g_?PV1-;`ouh6LF?`%VuHqVZkfyJPAmT_wDZT@&Te!3vORCz$>i;Au8j`l zyl`s0-&DK9LqldBFOKQiQ6qCE|Th+^FqI4mW;@7wp(x5 zOlUE%o>sE7i3&CSHDQ*(1@ZUgzCO^~yn#DCA6{(+!KdxFOfZpdb}CR`#DmFxqHO;Q zHTsJjPdKS`vN7Z#!EkOJ=TU;d|J>@qE|9KT6D43Kn8O;n#-ak6z+)dS(2AGgY1Fin z@o#}`AR>g%S_LWocE5TkUAv~CH*y8qmMouhZS=0qy9emx$p8j zUHUMq=+O*>n=z6L0$i5d;yBK;nx84pQYZI=J2!l+cXk$R0j8QqYBZf z0e{rPKQjs;og_PZ`=NIR!Ar0F34>3YIR4iPRd0K;Tu20db&E#NFB|bM+T8!|%Gf6d zn$am3j(-7O2EC-ZW*R8}*JYSmjTE+?Iz1Lh-qAF+;g$18G49+3LU!B>2=Q+ye-%6VWB(5%owvO@UeVaCkd9ThOMN$*SB>}6_z~(qu*aas!yI;__O+%mI<=Ka2&%Iu&=DY ziauiaqlB4s63gxXDDu7R!=yr;fHP#{R~eiBqfYxT(YVU?N3EjF9~(e%dJgh0#NZz+ zP6D9{|9=>+s;YYBOxzy~Lc)InLZ(K4TT2_lUb;mr_rHk(lGT_0#yW&U8)9nZZ^i3> ze;>n_xUaeG`d{){u01?rPu2_mlPb*uaz%;7FWVGe3BMZ4snYz9rwcFgtCvek;{SN2 z{{MmhFBS(}&*hFeU@#cPUZ95U=*JisL0%qZ}1vZYOd=f2HE5@xHj9|u-NH{$dGek^ez;Rd- zTg;E>?zRj$^_h$mNCkql7w6Qi4L#(4xAPwGh(%P=U!6sG5Svgb0g{8}h@ z_h7)*S2t&U(%6c3k3e~}X1;Yoykv7n0rL!O)M5r7nhM<^l-q=q^S7u$?^!~PtcfEQ zQoidwZ>Epr=ZGr%aLdI;8#v+UGKPr3^Pt>8@A`F zc7~9Za?l`le#HaooH_?ofx+-0)5STl0w@{LvS+{Pfb^Q_P*)yTh$IduYMa4sdd2yc zetj5QjQVGWIPM$TFDT8=S86YULBoUjJ3%^6E<81V`?-j^T8hWvRZ5$tA5%YuCgtiafSWiVGO;=9B z_e7iUel$!TdRRdq3?L)XsA+SW2y8W;!7)GxDEt0|_Jwp=gc8rHeP#k#NZ|cWfEe0b9 z2{OKSK&1ALfrjVdO+|+JW%^mqZ4&*7Yz@*g3yg$Ce;giLOn~B<(ss&UkACPTk{wq! zOA;4=yFJA34ab491^sinM{97rd{cBzg1_knY7`6nRl5o&XI?*s7!Q@#L#U-oIv!|y}S5Z;CZ zLDz~cV4bdIMI^wbMQgwu-UH8xJPIX95}|x|fVgGiY(e1R#Bjo{`&l%xhh@7E{Eh(M zva5qguIdv9j$(42ZIw5En#Loc5x`!@T~*@sHV?vf!frazr(dCs)DIet<53~qC5WCw zjwKR~5b&Leqj#p#HhbK=7jW%x{aM1~h5jE*B;s5Ld7!QeFqwh>eFKM7QP5+<@eO}s z_+x}CROY@S0Z2TBCGOGTic!cm)ji_I*#@YjyElXfPEdxv$OBM`p`g$5f>d{2o2@}5 zV}yY`6?+h@aL`iY!5HvReLQ6jY_bg}%0Qe56ywMfcib|7*w)^0nKbe5+z5+6A~%p} zYr%#%0Byxj0zmsDNHTFeJ3Y_rU1R(+Ou>M#!YHd4N;lT^P5@xIXy5*K&TD@NW@+Ea zeHb?fwp{lKkYuvKSLNU)6LyB6NpO6PG<@G4w=|%1YJhvRXvf+bG;;hWg%xVi z4+D%VI(F0~WY5FQ4tqR&>vB)t!ozV@pPvSvFST53sTg(I^?bd6S0-$sc7!qBM~3hp zH&2)#v3Y)k$;l{=nPNO)@enXaRkU$5)0=SSitl1wg9jv5dGsO~JRXU&I{e~+ceiiz zokbD}vfIR5{+@E;J@#?kn4?VO{7SD^fyXHV z|06v9K3*gKHuR{=Bb+l;J^?oT2cgdYUV^BfOOQ+2?0?A~MfRVvM?4bqUy1bBcS;5- zIA=^tuEPHPvio`VS78PB?E?n>kYc1UYKVV+=3k$H<9~9Q>YqX(ru~FBW=^%8yAAXw zJ?t@u|IBOmBNT^#er19lY2hZy@!VyW^_tqh9yW_)Fa42=nWKn*gp27@X8T9R|9qau z(*;wz9y0Kg!-H>ZYhIUh`{zULJo2ulOIAPE4N*|Hp8NdQqh^*2HvICF_yl7e$w&Vk zlA_P?bGdelR@-C$8L;Xtz4nX8)Xp_#F#q#_jsE-oHykqOe?4yoy59XH8+&x;;NkUu zUVosN>GN}C0eLU1d;fX+b2Z@~IFf!4{8c;kBwSMsI2vcP7a^5`0z>daHRc<%J@j#tJmmcM?TP4;&Zz&F(T z2L0&YlI@!?1eQnk4nN;Qxr)Z&Qlv#=B*Gb~AK`w3&_ z;+p#f!}P(lmd$Fr?St8mimMXokq8xM75nxjknQ5&0jUz^o2CD7fYre;CO@DLnOo9) zd*Q1eYn)-VRmYUft!u+8UbUwJi_3Ppu@o;{*Fm0cZ`V=6$HTYt8t68nb9yoqGm73^ zK*YSP$_Sr}laHH!Ba7>QB5RUk*ZhmceVG4P?e{kwO(v7B)g^2{Adk~w9Vv^R{|t98 zrgJTdT(_i*yFycsl>)5JUfmr$2y?+SN7fIjdpDB1sx7z=y)K=oL0I-Fc)} z=-%PR+Fvn|qGaUW6pB{%#a*CV5(rjJ37vWqZ;e&Ha*aLfk^AJM&X3zgY~3D*M@nLk z#Vuw!ZQ%-vxrBS#lKfzT7^>=m6P7fclB)7l~8O*K=vz$PAhG>NFi8a{UVdE4xHZ(kWX>*NBd{A!> zMP37DM9o)>&bd^hS}OrL{g;w%UP=mjqh;!9R6b%fP(3)l@kY#VvhWO6e(zB?8RJbh zmNsvx#f&0aH*;ouP07H8IlvH;KGj0frynBEIu=mQHz#uhV5loghd0)n|6)gMMJx7I zmOT0(7@;gFsjcR(qR*DD6l9&+@5dKNJkwrqq^e+gb_Qc1(v4AX;-IQ5KJ)acnnnQJ z40vdMW<{oQ1^+`@+zd$}6=v^7g-r7D&PM_(-Q$yV7G9mK-$aD5GZ``j7pNLzn z`z4S>9)JI3GX#qUU2ULWXaZ=))26eU-?^u}8rahx^#gpR-0=06QiA8CNZ0yiC&>6f zi79N?iYA9u1g=^S&;L?xR5mhlZatl!V$|Z?bmgU9raY=s*gNcp;KPaV4;&_Ws{BMe z2Q^Ar@;l1+CaTfC4QoK%7y4{dNoxD;;b2{J!0ke}uACF!;qPJ_<-u9Va2bY)Tt7sFh@J#R-pLj^`TjDl9X3-Xt3Qv#k>jHHiDvYowH9-v#f( zgPNp{2n+}3K<2|k)txr%TRGAl^dT-#xY7P%H#Y8jry@`pX10I)W})OQjYq)bqP5yqM4Ohd=!g8UucUBA?19 zR?jqAy#a??*6EAEocAvQYJAGQ5t{qHe0{RY-#3EqJOWYDKvVsp2yML1xoWd7{$Tue zpjZ;gz5-2N;x6Zrk|+IFEc--dbIA^l>5tCEyhSW*P41W#0etP6IH&gscLJdOS5=E% zjJ}{$r8{&!_Y$z8$&`o>sZo5}ij9d~5zkx2G@D@2SsUWdoW;Rfv^=qrP;OYPp zJ3il01DD6{Sq|O=_k2o$m<3^{3#kQv}_}5k1^zY^Q}mhmebBxd6ZP@2hLSt2C>$tESou?i^r!{)C{_GPA_g5XB@N-)SPI ziQH8S$mv5`jzo~t8}EE;&&`kNV~pWI%|@gOduS^nA(giik?M^hxgyA;BIELo+L@9UiZ~C z?0+5I>JJkI?R**m{l2YWF8Y^p z!(9N<#V>Cs%cNj%Hu6j=$2+;Q-;z9Y=Tv_U0-ygwj!U*F&f$fH)aDvJzC5dmscHlL zGe4V(|I;EVIH>vhO7ua4Y8^u2BtC9=Ar@(%_THkjPVPbRo^>_2AT$h6NYzqZJrViV zZA0n1znmv zdmY$k)U^|9FH16)^iDaqJpsWNDD>j{1%06mR+K3NYiTqSxN<7#oq= zb06IRK&rMtGt`>zU{LN*@wCS3 z{%Y_zhigXo)(o%FR&*-nv5J*!?#xq-f%GJ;HfV;MQHRielR}Qab8+bW&6`i0e(A$+ z{!ji8{x6H@A1f!1p}$$V=2tvwoshkib8oUq^v%U0^ev9k_DkZDkG9{;MeINVm^fa& zr~O!*Ja!r_!>7J6`(VqrIJ7%>k`6I_n9Om)U1g}iqF%f(yv*|mp3*#}c|4)GP;ads z`2W!L-T_TzU$pRys0avAklt(*0YRx!0wM}1A|Oq=NUx!X5E4Y%SZFFmdXwHek`M(2 zq$8a`LZsKwLWB_bt}{As_{=Y z6H3nIvcWbgX7sf1FOSM=7Qzy)^Nc(8jP#AZl_U2BByxn3Z}|14m1V5Cd0T_;rYRub zZnTG14ct;gmr3Gp84KFxg;xh<&#kQEZq7AT4_!xR9NhMY*y`bp4|htVin|h2nsCXe zBq5ry1Pd7j_MQOV8snuzFB+S=@cSP3e@EE$BasLo3i7eBg8;g(Q~2ENj)l+Jwe0@T zxd9yBa;o^i4>$Apv=PosIOu)tq|<^ML*>;|r4&c7E=7{w@vdo;{vFfR#&WTs8;)K7 zfQyT5p4Qz0ZBZJEd2T)VX=HuQL4kD@*UZG2AblJB+qfxbeq%LJBD>uR-CO(;wG(!N zf)4bT!0^Q!<^eUHAOULa4&HCR*$W@W#-sGYu6kD*g%TpB*X-}~_G+Uy(HPThDV#?GaB*)YS~0&TAC^=# zI10Z;?3_;nz{UL}1@)iUqG29g>u2-%;8!a7>*#0Nd2KD((qj@42d=qF>IyGjatCj! z&AYHl-1ZZ=4PZYN;5IJ`0nPq2!nCz(X&v`AZllc&IfdhT2)X;T?lK>wL&kT0;L?6#!br zI_k$r=bwTwZTvb*)-=P44NhkYr)BT)pTp5^2^Agc3j>rCnVNk0(>$_ppLoaT@OLBk zKdAhb`C{oIu^^5NC3R3N{b#B02=YmKQAFA5%is9n-RIE_N9Iy33Ln-?CTJ;8J&c?g zM?L|WMR);Npn`x9dAaX;zzik=o8OW3p(JXAjQ!r;LAmZ7ZJ97$t#{D97E%^^a3hQH1mqL*F}^>mH>wNRNXf^#Y-rRWQhv?z?5e0 zQ#ttc$RW-OW<{5(L9k?c2SQ!{-DF#ZA4!N0bK3Sx*RJDD+%lRTH;+3l8Hs#@TRdEC zT7wZub}D`kc2GgY^#gk5gQdNLsONhxMV!lib_)4;nbznwZtp&?To6UucXSIW5;id{ zDWcj-$!1FG%@IQW`EKzg3B}KjvubjSLOw3>tRs->i6w04WM-TBDQ7yjcV#r(-3f*U z^UIE<&^}}u-wujAr-2Zpco~To=Vj6!L@_IA?zG<`8F9U%FcSa#Ak++h2bbsrp1tC2 zeA@^)8Pk>ZqgrwWhJh5qN;CF_m+_D9F0hhI55j^;(hC^Kd5Y=WM^c)VE?iH|BF#EO zxt9gRBKYgWRcJW^u1s3*pN*`u*cPBwUdn8=RF!>%2&#KAYzZo@?u@;*>)8q_liS<)NK!6BD!wlIgW1Q4zdthpl*tvjjTZud4odykkZ~HcoWD@}c_N zG3)y3?h4S%$yeieckcM&7oZ5T*97g73>ZV~@nk2N8K1<2-!P<*KFz=0rfj#==xy+K zEXTsE%KN~B6dwRYNaFNFDgW0`QEKnlaRRmD3*_RjLU8uW(0`Xh-Rh5U@~`mUK5>M` zSYY_L{+n89I)4Nz^wUZ)Yvw-G6jXPmJZgGr!~9RR))Tw`ANIxmwnNh21jd?=m7NYo z{W@P_jKiLvWsZ&;@Q>cuoCIA_w&DBLn=A|*1qaTru#7jHQ_ym|bhZB1b;orT!L_<~ z)$1b3o~?7w5YvZ8zq0>1Vas(X+>xGlLv{VpUKRC`{Fx|e%br)Mx#hM@oJYb5xUJl(^J?}<xxV3$jJ9dg z){09{EAdp>C9*zlJ(>a$5f zJ<+BCzI&Zr>wi`x3wEKE&FiAq?s0Oq;Gu<2$HUVU+b6Cj#DdSB5{hX`1-Olq1ykVp zds*d=GqL(@`fYwdWW2lshEBKI%=Eo|JK>bwu;!nB7n1ipE6DPD*E&u(OF(FKq`+`} zfb%VG)B`1Pmp^J8;-n7IQZY)ORV49l%-xK1$PV8(9GkC)sT?LI?5%~h6*oDA*^v&q z=^ut{af~PnKi?1Jo>V)815GhiOzP9dZ^S`40#C*5L_)@+a!LW1#N{ZKRPLvJE^nx| z?1kgqZ}7o8E){g04A@4;iP-W9kQgDW%0SiE@S)8|q~L`><^~Nr14}bY-3h#NhNQ9T zuJy2s#gi^sg61D3QToY{4C;eh0SvP)Egqa`S!ZwG(TGhZ+jYGoPz~aKvT-qJSJ%2p+XGC~dCaJ)13GwIiUdA2 z_gHoA@jfaVJog?0&-DIa8a_nKBwahJ(0hF~#5S%e35&Dmoo!o^DZ5{7`6GSya;w^} zkCy0m6xW$4l&_Q}#GF2GyH{qbqvX7->uVWNXt_`dU=qy?&SKeG-Gh5>joD_|3w=gJ z2cO1sCXB0J8vhRtiCtHcH-`~VOaVZn*(LT;+bLX-J(lNEbgLijB)T-O%0OvsK5)jr zbRmYUnc9Jsd_8a?QSvF8OENQVtug?coSrNLwko>AA!UeObvxnY%lEZNUV3K;4oYf2 zQM4k#f-CaAe|g*C@qM9~K*yzz;BBp^vD0)>Ly%12qQjv;X^^cXcgO~SaVhFM=?T>K zWPo3}g%P>7Z{@&2r@~`wqn+R2+L<88k?yMEf@|a1Qp3P6ZdKAiF;OdjSt-3riI~EK zS+ERg$mh`F8@|4Z_?2RC`2Qw8Kh9Q!3J2fWx!km|n4!-l8c4Gw7uHc`o-)>m;90#+ zv-kYRs|kkl*=lAgG5mUy#_z22MO1x-^D(;qDi+^9dFqT@z>=J?#ew(E7NM`YfnTv#o5NZCL(czov|UWmP8R}*6S!`#5v%AqYv=(=O_ zbOfpLSrEHrqScR>b>+m75?#u>VBMm39&X08Wneh2S9vZ1NiMzL8OgxDQ`Hap#48Wd zzjBVvb^BSZf;(t+-UO1)uf+GO)O~n+wC1aqqDBtMgU-{90X3cysB!=MkDVq;qffap zo-@k18cDO|`DYjf@9!;Nu};al^1HxDcYeFn}4Z>K74R*f-`Q91!`frAc}CK`*Xd|~YI?i-s5 z3^o(~&(PSnxZFJ%9GIeQ-v6k+1puMT63mZsbD~6h2 zu224>-%0|WIdK23dqPhC&B)oEcX8rb`^~rKPkGE{7KgA-8@EQx;=??rt^Ld9&WC*K zB=Z}TEgs=-79>yGT0zmfKB=-Ibr&;<_2v7G*W8s( z6ML;Qs>QE*8t`hpOz$YC{-RILB3}QuYyE#ISv!Q&=VmfXzL61vA6BKF%=Ass`~Gfw zb#VVpn>z%Y3Q9+%Zb;L-nlEzgy7G1PKd;JE;~pN|ZcO|98R}(P%b&R8usE4 zN}g>Ccv={|QpKh~-18VJs7KK5cyW$>UY9AL^?ejIEp$i({ya!->sy((6HF>)MvxpBEg zuT9KC1(Lq0j1kbQXG&_PrDy|WpwD^zP(Z^Hjbrgv9F`&Bd}S~i;y^hxWo=m0Rm`ii zBN7@v;YSOng1Th;xua|!A!Nw&h`e9e`-$y;x1QpW?%}TzcL!m`>xA~Xh|>AG5$$GB z8s*<>f)hevOhTwV;SIWKZkb`Fp-mcW9bQUm(R5U%q%A|~+5l%UCN602yO+BDM0cZ@ z`|*A8)bG>b8ReG4JJ3MGGCmQ>M}PPmGAlbxr`zYEWNb^k|j z=dbX-F85zf(Beg`Dy-&Zlc;DqwFrH@{@OW+$araf8wC%K)@ z3Gl?dZ?9~I0W??F*C`p;rHF}19?7p~rxgUAZ{{j|1k{@#L%;buTw}%-5))#Yr_x0- zYA?(mL+{C>Dg$%>ntK2$<Cv&ANX)M68<7+@Z9 zb6-r=pLoNA$xIk5+5hG+@;pifjze$V;a@Fd39ynp`R;oO{lot0+Z^hS2h@sbx3M0x zjD|n{Fy*;>Th)M}l*OtJ{n6x*`R1IdpYvk!#p$_^hvY7AZuj0>aE-ZxCj{!KO}Le! z#nUSh2I|!CI$rJjMk*fVr9`Bsr zW7s#k`9wVtblUu`t!|*Hs+;z=+doM{5pTPCxo)lp^rGdp)pB1wMP!+px&fiDPMgs~8r@!uWNOf%zBsQy zy9tqEeMD6r*O3&)diGg`?O#3!Zp(c>tVPrhS9fIVxcr-wh}9+JENB_r3zz?#CN!DaZLouOEDG6N9wN zkLQ`mN751BfSj)xV;6Yx$vbX??i9+tNW_vl`_4nwxZU^>**4f45W>+}dPl*r+gR6P?IU=t;81V|I4Pw|h@%}7t7Fvt2|*KnsCE3x z^5P+&)yay4Bpb30&LlAYmO&O6j(9-ODj3%8im*-NP3W093ITEPWrs5@`n=wD5yiA@ zm@T`7a;&T64YA)hHf`2n0F<}-5x1BPmJrP!I>g24Rz1b9n+>NFX3M^?V(oujZfLru z+3xbg`T&Tk<6dP7OJbDr`AeXj)CZRitHbw}xjI0Fj#@n^Bh+XIWmTjynyb9-{#!^~ z1c55Qm4>`f)ByRA1PyL5lxz#sr|c^@&n_lB3H2i>1d!m}-adp1Is6{Zr1e_gO{*nimKw6_Y~Pv-a1 zpTC6n?iR*M$eYiK{ofg}75T1<+FOlkF1C zWVxa%^Cf>-L9!&ej^KlXaBIZshyB!Bb_^lw%ACbNCX2JT&qp^iXw^x;Qh7rMa5OeL zVdMrtaqXZUqYsA5+oF<>CvCI!_RhsV-?^a@avi&El+m)9g=~B}7hHX9Lv8i(EY;r4 zUr8WHBE+c5XgHC%zh=|7!WmsTR4Upbn=DRb>$zdA=|_M@=;ca{{@;WlF!n}XcAY4gsI2n zQ>UAO9aEV4)i9L8zeKdaAu=e`p&Qk<7anxS6Iv2H{*r6yrC@QYz3^plHu&p}PikJ2 zZ}DG1mWE^AcMmf&UuXQ?hZNe##$WX@ejlcN?2F8#91dxrZf9bOInRuvh+?8c;->cS zk?;e?kQSzKQNjBa6F0xQ9NL`6JZ^ayG{SDW&|b#4$JQZrsg=*q(jTy3Yn>|TwJ@!F z+ZNi@Vv|cH*L%pC?J0d`z}R&SF^@~$SyD19k`2H`#=MYg+VDQJqwMD0%C8?S|8h@x zyW_{yrxxq%@wt4ME5DC*S&_H7WIt}i?YS%Qq-yyGt29KP0Um_zKw}8B^J!8|335e_ zkUHxorxeFz{dg;h<0Y@6;N=N}JKFC`rV=l;^2)B(L0?}lpw`v#5Uc)O*qr~d0i>$l z;WH2g5hkRpJ6iPA+`J2yos)B(BWQ=ik*8d5pfH<;d^Oq{n3JDqo(y3CM8PiR10tOy z_XVRL9z3NT?s!CH-9l2h5g{4TMrUgC=<^FUc4lO}lpoNp&L8;O-*a5sA@Gq2#mSE8 z&dJkt2C=f@WHkaJAGBK@ju?~by?VoIbIBi_%2o)KD5pP2SwFbArR?9gj`b4u=eu;O zIh&Opp%3CJ&!I!5bo#M;;Y38ucYo@Ghm&oh7I~qyi#4}ir)^)to{6L zsS~GftTdYnGx0rICZVjB7l?lqH8CVJp1ZXg(z9<_NQ`b+4(hclZoE2ZACX>jp~THE z04Jg9k<+xSC1BgLJNs!0{j=@cODF4rQfs*A3#Ni={>_A)vi}8R{j}>wYieXQ%;WWa zi+f71{Y1aj&K{Fv_(iAu_2h`1Bpb9ISG)wZ?tcFOLV26xCEPvKHx3PMVSAD3aOyYr zjBr2FjqL4nlC{ginD#6Zqb2(EQ-A%>!mUT2N<4j8|4Pp5er4h>(0F^h;t0{aeRdc0 z_6t^$x!>*IC-na33B=4gt{(ez+D{iP9wema@0JOlS^@{F+>@Rc#-WjG*Yb)&_LXBC_!ek!Ev7}_n>W^8E-g>Quw0OgKZ3Pp91vk~Mq zBsM7h`P?$|H~mYNTEE^yTJ;Drn7tgE4#|cu90NpbwD@?hWXS4sMZ;=8cz^&D=%=u&G-wb*9El|zv!wYl)HEJMJH$za&fR6Ho#yxcscXLt zx=*t!2PWCmu6|)t0K>HTcU}?_OU=8_L**hSNDRk&n^ua%FW1)^VLkT5CukUI6jcxR z&e1c-29&;fA2w%(*l%?kOS=t|l3o-t!sY3XJv^KncPs&@1u{`uS98hjy)UfV6*Q+N z)ANpg?0(tM3-)h-~8-;Iwn~;07-iZLA7A1fdz&A zhz;A6;RL)`otZQBtOi3aTsq+dMBl;tI`evu#NrmXZ&%4lkuA6OvlDsn&|X!usjMHy zehYrv8%IJu!AmQG%$#G)KO4hT((yov$;jQ|r;qKYI9z5Sx<_c@#}S8gPV_3Af%YR` ze~#mp@floB2`dgrKTcteRsOI$IpFj>EULr;!2BicXrWA9!jz(bMb^7O=D&56!;W0ehn| zDRC&;pqkY~+V~5%#eW6|Z;O++5@&+rg4~*M*ZR#}v(U@Iz zW)(dssMAxQD~{-RFi&#cgEG$a1axyg#LX`@2T1PC(f?v+`g4x#82@L@i&AE)%cb>zC%9}?!|HeT?-`)0V)^Gj_*CC(G8OJA=| zwr4dFv$iA$dsSS`{Q($x*@1h3=dOp9s{J4^x!6Z9#EcVy%%G_D0BsQ+{jV*DT6pA_ zCwOH)8&+qjaj<*KXWYLzoLTCX*-ZJF*88qLAHLU)?(?=*0fA5dux3Cvg?UK}r*~vjKW;X! zKs(UfEkCJm5|EfLVUZ*Px|&I(X)z-& z_oJ^^HHvmiA-{8j>ToQjkg8CrZ_`X!>9SjmK?@U1r6{bn1C`9 z(!Pe}iGyD*rmjyq^Bp-7J1nIoG-~3|iQOF>m&2GJB za*cf{U!s=af0c+oR4pZ5f|WhntJ+_*-1%LdrH_cc{Q{h@LFg?v>mAd4+-%FE-xapW z<-beJiE#f12V}z!S$_R_#M+T8(EHpo`_*vM;Z5u`Q=t^#ncuypyG~h2ztMZ!MCHs6 zH_qq!#9f1wKu6-@YJ#-IEi)utg`uj*hgF1mS!VWJIY#pPr;opW^%9hA>R=z_jJC93h3ELL+fSn%t47YlBW*)o1 zckgD|&d5(`Hai+xMrYz3lyxi9@B0JRlF77@2W_iAjc44Y_kfYId!$uCu4JY?oXh=l zkqa;l&UPie`R_lb#~|5AmG^ZgE^JzI&qbvaDfG@VKMYCc`Nz(`-vD#S-Z)=S$N3XM z6ZbC=&5XC+Y~vklbuCx3$^^^mCT~u=0j{?!6sN)=;F$MrwV5bf@Ke(Ok3#NC)VHc% zIcNT_e(gOkVEfba5BCu7dB7q7DOUA|w3sjX&kx|8GNw}Vg7;tVNk=#4<6k54D?0sa zMKl4J-aG!cmQLjZ>~?nBk6H-m*jrB8hs-SjX+v236N9q02Q#B!pZL!9Jxq@#t_V)? zwhJoL_Tj|+ilZ;X|6P7R&Vn)K4sXU$4d)12x52p_ZS6muR7M$_;q^?#UqnQDT^yT4 zE(&~Fc|w~?I_cail7G*m$Z}3>1{GrvLB1LEk2}r@aK|xaLWa>dYwAoic0D=cj0H_4 zwkf3mGzOPsG>B^W_9cO8y6>{}zqUH{+s}sH0sL%z^DLoc=g*Th3WdFe`4S_#H-vIz z?UOw&Pfg0`{jGODvt_U|A@PT{w>gVR?0cTDt`2~F&n)O&{dY9L68~3w7Zla7>?Yg) zsDF!P;)iCp2~HL;GR~QfU}aAe8Ew~!V6fbf`B(xqf8+1@gGhvrQPQ4LPBXB8Y6HO0 zguP{4r)St^wViwaog3)t=;lyC89PkpZ%Nl}m#|abP3+e{@{djnBrcWDR;|j!CiIHl z=w0Qy$6!%IR$hS}s&^Z3#c9hE`HI81oIgP!dWeXV^CKbXC(W<9B_-Yeb)QrmMRend zweo;Z#;hZqUL`V=KS@U@rrvbedO4TL-q#(lwShG*i58|>sYU;Ui8&dMDkKzkD5^xdxFBknlCtY^#jxM*%8`K z%k7t~uHUebrRO|tVKNCk;q8htkd?*VD5zy_%1GSL;Q)U=;NqrO8y!1cyuJk3HFKfl zl+NPEu^Qb5t`w^?AM*6DU!GfW_^SgxD7iG30ObkoN#|Ce-C%DBa`cDH^uK*e{doJr z_R60v$u=q|mG~_#K7IV>hDZLyur+7#P6!E!vO*l6hh0t&-8%rRQ)K_Pw1NPPum|2K zCZ70d2*~=9O@dxyN#sd-_j3Y#wcFY(|Id{>(M;)i=`(}K9F`-Dmj-Y9{`r9tOr8JML$!ywjIf8(`Sbe zaq|65ccNmE?k$a1HgwAr%G`@oCS~vSBZ;b0;KTvz7>mZmx-Xp7Li9aka5F`3dIR#} zw0pIAiNE#1*IN06Yqf{_U(?8~ZYn79vE7$pIe_wIqe}dygGn@jujKXB{AE(cYTpBqs!#`-<$smAA zR}ez+^T_=aiA$i)X86-Jb)397p|k}fsnoEG^kfE_U2MS{Q6gTnc!zSpuXv3gL^i2U z*KilSt}KR`0#K_dSpLZ@g0^P4^6De>K`?>(_)dUH#I$NV?Y25#|g0F0IS`45KeKJn?LNVQNV6}+UJX6CO>)=@7KsFC!h+}eX_ae4vf zceK9YZ~#b(D-x)-Q=yejk|@gW8A0=Htv+^5peL4{_q2*=q4Khj*bN6Q7t_{Ajjcb* z&8ew@qu;%I0;288PP@dZw9P^nklwyU-IItdnpK(xCZ);=t|8f?p^~n znFj0g=qbE(-}oDL^|@(DH}6~jXQJQ|n5Yp>{!}Jl?vntVkH5IrJPx8 z6I1}JtngNQbF$#Huqg>_JYUrTF?pHo!)Ei#P;$Y=BrH>a#^yk7F@^(^M zZ?pO7RLQ4vYBBD>rJEc)-pN-%&PcFn6OrVvrR62goKyq@knHPMyO^kjGRgX1p2q))oWCk1 zuKElo=VOXnCN|>tTLO|4ND9C8+Z#vp!7CwCCV^ZQ_}&V!jx1$ni!R1*rTNrHrtC9}eR=E%DJX zs2=zTQu&(%i9_h2(Y4r+9T%2K5C6X{qf^@gq}7%&=ZqWKWX@3XPO^d+EXWFd2pcvl zaSNQ>X|++&A^_QX$c@wn=-W>6xxw2uEU9Xwv=*kM&VZGjt?hz})Qp~@4fh+6knxh; z%(~#NH-wH&T-5Upx5L(3H&Aj9g-b|-{D?=+C>$)8KHtsfLf&(LMd-A%W!bAAGiG)s z1Bej~^8kqruBD`m%tZ2dLA~2AYrKM=19W6I?@N%IId)YkXTEEn-&ref*YNQ>3!FkLBK{?Hj6;`01Y%S^Om@PauHWn^ppX z%c?9jRt*^@FMXUWxgaHTn&>%r2VM{sRd*Z(nBcveDF&SJ)jo^Re$#K1w+eD7%oa>O6GsnPC}Lk9MC`Pb=#%&AzyAb_ATz&datZAZLLLg zD0pgcG4k7ol;?+>+U))DyW<6!>-o>VQANaL68z2VmwA2@9*>WN*?Wt!k2@hA(7Gts z{Gbn>jVE2|UaqArWRM0y9gtyE+>RN<_31PWyILCYo1fcdK}^OspZk7Am=^C+n0 za`N#20lCLu?I=bf%Ry~V=PSAJE))`f9tBJ^xB~Q1oejSDbemXN!*p-UX8D&`Wd}o| zX@0k1|9C<&^WN5nO+$ihI)ZNA(06VKCDfr%jCapy8d970<&B3{O|v*j{SK>&Bi*Ql zCG#vJ+N){n`;3U+!Bo6q^5TPXl>pz2$Jw*)30OyxzIwI8ltxOX##?`+-B&0Z6%q6RJoIs(37lE); z{%7fh*|l=G47aG1s!%d6<+Lg3zPfYI>)1~L7ro!zMm9yRS^F~^xEZUd1}xa$$(KNQ z(0oCb8gDH3w%=KfRT(MZxhJs%Wblj|4gq*NunIt+9p>Kt2HKKq*!=CnR-QQ0rk&2`fw~s&Csg#*vL*TAp$5T8goXIHIvNah0NoO&tN0Z&%Iso0dj_MdbIuFjKEJU=IWu*)r4!cv7brg)g}vx^;P)$^FrBQKFnni?_ih zSrVrm0#x5`TgocQ8uquX_FmpNi^KPU7{dqDNiH%`w7x{uDocNU9x1)sTCEjU8nV(% z@1VQZH?P(0sXxb$07gE>88#GFR$-ri8zn~tCK26Ze$y%zJx5;g%GYCEG&NFr&zoH> zXg8PNZ<(cWz$Wj_=7-DybJ1?kk!oO~m6pe9R^#x^)jNzNV0Jo6A^hwT3{T8>e=r$; zO@0AJEVGB!aSN+JVCV%}FtKcR6~Ux+5jSuS{u4$+-?T_OhDk&8(*FMX0pRa*H|oc^w*(H{=$CD4 zBru1P8+~W&zqR+8S)S)-BEE?RW~lRmtUU!5Blf^%-EJ(kP@&w&*Gihk?pdy*OI3y0 zO|Hw_;DarO8J=fA7c1S5d$Z-aEw9n`;5Q4Ac7)&{C)`#I=+BPcVR1@$ecN;Bkm9+1 zX0S?tDEF9bKGhy90Ez*|Y1{oSMSg!K<4E|$v4a$E8OyME3$5uL50U}{f|lbo74OH`K}kXb*-}`ZeEO7GCRD`)9*57z%|56 zijEMZ4?lzq+^K#>YgYvDduceKf0(eyBmL3;A95+4BC^!I5bibX4~Xc!h~ifys{c8c zIbLt9Cb)g=``5dfsKu;0a}If(ce_jz09}3AR>li}3sc*qAgjrQcLbL*wm$#mI#&fF z3fARsI*9X5^*%XND+;z0sG$kYZ+S@TYlp#VpL2MCvCml)6De}O@2t6I&AvL{5IgVR z(M@lPRb4M`0#&geDqki7-_4tfLYtkz>+@ThC$VjjZJOEJt>Kscti?uT_f+{H{3jTC zKN&lhf+&tEchhh?qP9w|s>+)6rJN%|2rEZ_xR!f)zyzv?UH>ePqeti9l`( zA_DAV)X|>eIr$H&qd&rK__X0~6^9>vzJ*a?1-JfWr+UZ_F<$B}Fk%OiT|` zb<=%&RUO)b17`qRRR_*{U0u2V8+y-U*^59?ju?XgNSH(O>h-~PdZj*x^buIbxIZ5 z+fAiuYGU^lT)Vw)Iy;OUOKfZ!-g#Vh9N#NgST%12eM5F}+`~I~kA1_FoAx04qGM?{ zt?xFSLQr8HQ0wgAEMP1TnDrkURP}Cp(HgISohFda6Vfr1o?9;Z2a4YPbcfJ?PBxKl zK9a{do#$nQ>Dqi5}Kd()D4CzyfUu1cwS?he4!Z^HL9g*xGO?^lh0 zacz%`xQO( zdcD5dye8TQ^ZeQIz60gga$fC|-4~`Y^MU^`mMKbeqcAz$N1PSr)X`fe$C|khx-+y{ za*MZ(@R_B>-x!S~UoM`jbS`c%D;SVFp|r%&3FbYXJ`NlzjgEd}A&oqIkM?19cydrgZp9pubY--sY>j;uy6Upqg&AFm-*>GASpBUj; zw}?OgHT61s-r~-Ez_ApIr)g*)bT~XzJFsU9`%(%`tKpuH)Od#2?F`-(e%X~uKkmG@ zS9FKi2q(y|KeHy!%zFvad@; zH%WMa|1$8nwE6i3X}trEA0wcH?l%-=MvdV?)JioRIsZLGxCA(fWdx(U0C{@}zdV44 z5>2`H<$S-_7#~33^V8#l=)*&mduKMspn)Q9+FKbHMjn~5b zQuq8nhFk?>@Io+#UNQw0b(Lz9CS8zdvTh$VcVa4gQWT>?+h*HzB^OeMh}`A7fzTjT zoei@(B@skaznuAOcQQaHH8fx{Sdl_}2}SNeZT3Df5ueBH=1F_}GgN|YEeT)eqq#~z zcsQo5&J&4n^Nc}n8SX*%uYDB`mSC4gh?ulMd2%msP@8Ac+4bjmfPkY zVy4HS6m~%y{kCQLwby_NtM*fylc$-AS#TQPJFuJ?f-zh6D#^Yh3`A%st0J@U6D3GU z)e`ltzyrZ^{F^6F<5CR9x^}37dE0O6@DZGhNgq2w%l7(Q@2-s)7!pv%OrNV$>9|s5 z+mkuR1W|rQS9KcxEaZ-dw|S2b_uL>kaF;DR6A+q{G(9ybezaQS;p92K2SRUCuFJK% zbnfjMyp=y&M=0pvWTe<#Lm{vq2fBojP`eB)uJw8+bf6>gT`kF`baE}W>FgJs2ZfKZ zig|CV#5|wqrV6faH^Ze|L@r>aGhJ81@SysO6h+vV!w4Hw8As=;jO{I!1; z&OPK(dfnH4bMtwpa0wr-Y!f`X2(sKwR27q0v6v6Mg|_i4Z_nG&w&`=scI(?PQjc@C z+-UD`F)0XQ>`8MNxR>UOl$gu5x>&%6Q#7Ezr--kZ*}bcsv>s4$@G&0av|oGAn7OlK zu3n(*VaK~KI%jan#1wgZU*+lTmNy-U>7B2hMDk(;xiBs+{ho-Y;QMWjmQIX~TNBaU z%H`yvStfDhI0tZ#p5NY|Thd?at5gnCsCjpj#w+l+=vMM&ACE5HM40Zwp$Qv@!?G`L=Tm(0s}(#gEQ==oq}Q*le5+lJXoHAtOr+l$F{u8>job)>n)ji8(jp zW=7rFaPj`!^8!BC-tPzAeNWq(RVjqZ*pGY57)`zQp{3|gw-$I%tFJ&2LGuy8<57!! z>w7t^5fHI+dhNXGya}|{8JLIfUT;!X8(}b{nv{~(l?Thdj%7hh1z7VY-=QO2oQ#{^ zJF(TsiQ*V#iaMCWxCrQX4KB5*obH1^aOu?q&(#OFQhQ{=tzJ2KeKJ|J9zPlC{%CDw zz9i-<$j0Gaj{th?pvO18t8gLJ#Ys7DrZSJ$h=B;k4x`9*o(|*U>7;Rv`{k->ykcXI z9UF5aZ}xP(t^MRMaJ^e$6+7u&Qz>tG4RXr^i^Gw-yM{}NHmU1Plv{c-RmIxZ9DBDs zGgnuB)U;5_PfEN~O?ly!ElL|XL9D!zSBbRn9Kco3$#HbQaxg$94kcdIs0{w5en3r6 z9UP^;crk4wD?PN*dXn*#4i8Q-h{ywkg9$Xl&TDx0mT@YjDTro%>(>3R$pWmoZ|(@k_XnH z-%97u4wQ9Vmu6iHGHJz|(%GKBxa`~kCoNLfnH%x7IOsC9 z{A(LXnp(+0irIO}UOK*xW3V-zI_ZS;Pgfy?+t1BL%f3~?H`x0_mMr9G?K3aoJ|O+^ zrr*9dvG4DVe3QM+1hacUPm<&%d0+cvf=I`$7zUjzHR)w&GhV;p&|7Dj+Zq~yOdixC zNjLi>44)y%g_14dyCc}W%TE>0=xyB#tsWz@H0=D^YhY*jhLbtbqNPwqZ7?N5o3xVP zbPsD_h`wIALLoE;_~SE2HSRR%SV?0-2L|3o!Ooj0v<@G-Tj11#hGDIz*AvgfJ8E$+ zOy)(4dws|!L-uzX8b9rfRT%kWUcgyI2KQrzclXB=som^7w}<8VCC*4RhM4B^u0l$` zhhw>&X}rbEI?>&*njwi~eS;3BKIzqw@Xg3dd88&0QfCkBYh4Wi5%C>5$aaXP1D0Z3 zWyE5Of~$^lI!0U!=?gyl-eHoxQ#M~%s;^#9>alaykMFy!kWWOivm83_FBIMxID8;5 zZ%-#jS~l(Beo4-%ic9)Cyr)fuggcU7u`w=*U5fbkr|xM{$JGo!T^&mPlvyK%EwRQ7C ze`3TZ(#LYx9J&BTOmR8<)WaG~awe^mSlnghOGxOw=3a>awr^qV*+77{-5b-otu&x_ zRQ#ZpitcIHxx?y2FS4xjW>nWNE`S)Dj~1#Z&E@<~;@aXYYYlAt^Yu>KwuLAPLI$s6 zzeu#q;91~nE@{zZ-hv+zNxr39QHw4U9&oDc!Xv#-1cFuAhW0+lV2Dk~0x51>@=gV*!vJ+| zq#U>r^4036SXsJ1+YWl;HM^1 zUG(1{-RO5CHUH?~_;uwQ)qeKLl1lkqYGWo9Aqe+VHujgxxL=2%Oj4p>-lGUe&xDZ` zyBI{YuRYRVth)QYCs$fFP7f*W)>0hMO>x#UnJ1oL!R-N;>%{o^J=~BoF{AxpxrOW6 zm#KoYzwEegJ7KzO3hpx{;BpY}vYNh%_R=m~Y`w<$if|RfTH}P0U_u z3M|jC3WHY!QkPRuuZ^8r83%*RfSr5!0JwwFYuTCKV}~Wm`N!=ny)ER0>ET!vW<&qf z^XT2MngbaLQ5pk~k};hC_QTHgRfVGJMhknZTh6r-SG;SryWxh0DR)KdrqYZLZchuO z6~%X74^j56skP2hp=lg8Xv!~z?XJhYl7x213YRFiU)gd}|Ek6tDWsc1wCveU-i6O~ z-E(e2Rhs3YTx#+m{JMZm5{=cNoKaJ< z-deEy^ycPyWSouPw!rG{JYPM*Z8xeRz9N^*U%Uxk$IDv>1zB3`2&n8^E(h+L5a%8_ z&C5MGshYBbUIg!!TXH8(8)=`y&rO~v8qbGWiO#1k`;v>iT)HX4>&VoDk7jvp{={ui z#q>t*-o^vG|2Xvll%msndt%Bg4%ONPA^3M$I?Q)LsyMTq;z`(r69Db$m2jJ67@OjvQeq+Rq<$7q6>ig)wYv{FeUr=6R} z%}Z#DNX@Saxz%}(`(|aJwy+$@?S+neN>ZBj_{mzEk5t|vHZKx>tV78BALcBfap(-r zCPM%PVk$`;d&9kRtgX;1dyPRo0zH<{Fs*%IQ6!vJ&@g{6r_FA*o55Cl9nu1KfaUAA zu)yNj(0{m96gTH>*hv&$HRsYNWHut!T&D%fH{Mk`YsnbSNp@V#KZ_%tDu7E*uMME8 zcck>2KZ|1f=k|@b`qvFqS<9M&E@?t{;1{#At;veFZ^ATA^ zp|vhRpizCt>l$6=J@Zz($?a1rPE!fp4e|xsDFY{K3h&KP29(~w;?{9Z`vZejczi%< zl*DP#M#m<+oAWog&YS8d+4qKDS+AjWwo3#*r zoT4(n;gj5)Lb^xnT9NoZg*Km%dA9f@t#8FU;8O)#e|eYU>+xSr-{-G<%?(bh>1i88 zL6K!Uic=Gvo0E4LWm5-C-5#f%aV(i_Y65D-yXPre2oEO8wXzDe1H}K=-j_x-k#+6b zVoReUji{&yv~3Ft3L*vsgs2EAqs%e}ML?!R2y=uqBBG2cATwb`W|bif2~iNyKoXh4 zoG8c;0tt{nfDrPPt@zUGyYE`}?_KNiv#51)&aPeidG@n+odeOtK&rrVo>2KGw0aRJ&8mZ6DqO9YYw4(le3}W(J^b(icOM;{Ljz(v=yU0%bK(e7~GdH-Le^g%3@Deu?6om7H-0)y7uH4Z4%+wmQ zL#^!T*E(PNHJyM1!Fg%{L4=SnXo#c0cX-*i8D0=Fo~chgA=Bp?&e%2oO$WlH?j6u&>be*x88L0ABhesxOuRlb=|}|6H{n# z*Yl*~DUWj(50UH2hdl$Or^z285_Ju%^T^IFWaeX~W096bhxg!OZe%7k*zT2Z)0Vm~ z%6ll4D!@Z0sxm6p42IRKP_af=2zjx^oOxp#0 zK(n^dP#(_CNs%E$pU0)z@sFi+Z23Ns%a_+$hJmV)(kI~a^hh#sm+BNs6C)y<2yq{t z9K2;6L95G^)YuygH=XEdH&a4rX%NNJ>ENYH%>{!XVJ@pQa!EKPYrWZeA z<@QO(C0I+2YC9C~`PRBLT8xiKM?E)#TI%YF zkYjlM*KT*q7R53<@$ZVGD9Z3(4?HHbXs62OQ^xQ1?$80bv`}+RD{D~Y_Nsf$ z1`9u^6~GcL%6rXI^*#DgAj#>TxyugDES~`zivo!gi;BDAj#U&jVSnJwRhH4s! z6iejsd`4&W4rkc*KQ_P_(5iS5 z*qT`mMUQ~X*mgP~xB{_uKvtN^G*BP1!p@FO``7N2)N6Mc?&d*XG9xG3w@&T$lZrX9 z~> zI8#n0_BqdbMZ&iTUY@7ch-#)Sws4)(m=S8d#)7?_4ZAjMWc@E2C2D9SoxV6bhB2KS z`CJotq+$Ie9H_IB6+_a@7felDh&9qpgU?P9C-EcIGT_DMsZX#@m(^UugaaUtUT~9) zBD5tB)zjAIp_h;}{Zh~7g_Rh%*0+>2%XU~|?_3L_7mqhSmUJ$`bT#j$PKVzxC=98y z@;K47o1rxFq*1#?nGrL$kQs9ZF5}ZD7QrDNEIyZD34Kd+whBEvQyrFyh3%MppBC~o zypl3Xdj+@5P#q4K4_4QZ#&X|NW^M#QOZ$(9ja7HE-{U9F?keU#tB;H73Zd^h^rPEh zRC)YgG^tiT=e|&oq&v&0ee3L}w}}g^V@Xm6qVZXC__F1k>eOB@7j*g`+??ToL4N5_ z=3AJH|4=GZRP{#Xc+#@IxzkS5aZ_Bbc!wSRwG1^T2ubhe*WWrCV7L^qZ+GOrgCOO0 z4?JpDL|HW+3BrSl+XVfCQTv9c+pZnT~s|^N!+E;#>qNxdU8m@^sB+!lO{`7&8B#^&0H6E z=K>MGp7x3AZy8`a{_TOr_%`B-FGyOoY(0L5+(@SR=+~455kkO`n)S~QU6;!f&nTGEbD3z=o-Y$8B327--qRCr7;#<% zQf0S&VuwlX4_CrvPO3_1UM(IX+lUk+2Ul85uN7OVpY_Re(O!B-C@JYJS#aHh*HrQA z;-g0c(sq+C(~ffLLIZK5=Mh7ZgTWcyb30q`X>T8^ss3Fla`tQlr!&6e!hPfJU3YPw zal-UW(w91kGV0@@fyK}iNY(*g!Q;~c`*Ck4(l}yHdYO+)aqW`7_unCK%c@5DS03n6 zVye&Ui24sw-8d3Qcc}8PV>rh9F2p2A2a}pfNpxUe&OLus#N&X_qOq12h zKm=2`(2chL;D4ckbqmbdT&apIoj#{#rD#%B7#hg$x2E>IMuuKb)u}&rJI#e6GWdta za7qwAS0b^m>0{}21$Up^Y&;L62T~T`<3P)97B+J7@O#(81)6wKvz%rRMGX3;A8Tb< znaM0ntCcY4=RhOUQ9DlG#GV*&YF?8)k*=J6mp+YiabHr6SkV}f6=J_UX;w&G3kf1D z?e<$*D#a-_tfX-ap}=>D9uzdAuRqEBi?=mRHJKE{R0$W?KNgFf2xU||-_1aDoe$UA z<5$sAFHX5|-{We|gcCO9r7@@{0}iWhvGMxdWG!wWJNun(Q@{@oST?BNO!NwOiO4yH z&sJ~eVjNfBKaw!7bjD6#%~J`-;b>3o+hB}C%>-|EK%pPuL3iQ)Mxi`HR1HRQ+th&% z{i*A{^)c^>GQi>3zo*&wM373sNBN*6myTU4yM>W1qze)A zb$hJzpd)LtLd;}2IlENzr7%tFapSWNAKb%X`W6E@&(-H~1)jv`ksN3$DviJGQuph) zoHFc{nurV;+1GNZB845-yUn(J5eq^jCCM9BO>2zeHBT1bJzCn~`@ZRNf3I6gM*w0hZ>gG|=E?zs^%aG+b6jH_M= z*}bHr05lU>m187qywAMW8u|~;;HFL?S|$H_SyhFSwj@8V=eV>or>?Uv+x+UPEinhK z=vSWsdhWcegqg8VFBU)(0oR&Nb}xAzjnABSz&9W-(}Jo3G*Wg2|MgOaFwaxlOcryXdY}4(w_uVsn;54ne;0$z(QPQA^tI0UbK^HF4Uu z$zf=YQs2}gSwxuiCu-WCJONh1tVPs#R9{4gFfXj5p^-kXB06pmlx}0uT71#H_B+x0^H1oj&7&V#^?z_Q z3e!Jdq%ao#!%OS_r8fPl!*4CBAH(Fci|eY)KB|?kj}H0?yWK66JGC!qnEDef=Q;3A zJgh2vd8nEDL>H5Re(!x^|~wY%P0N_%_G# zZYmFH^b~)2jKTM&h2(h9boF=+{ftOXguW9VT&5P?j?a8t(!x;vhpG^%HRfi{3VH7? zs(xjj%kpTt7hm#Pn^FI8cg%53TN6S?_gD!Yo^a@14k1WUIsdJ3X}LyCxPW(~*Nt$Z zU!t5vBlR1PEWYU%{0nlb=E3Wo#Mf>6CWFt&r8!X#yOn^bImB?KR?3X!?Yyebgy_2j z!=NW-F2s7N?ocb;z4LJco$2QO6gyYLUZ0D!v600wKa`?MMUL}C2s`f?t1W6lSq%Yk z7h&lw26NPU%9jnU^`>=CR3)Tgs?K9zhr5Tza$mdNV(@}*ehTsS?scnt1cy-x_-Yqt zmlW~k4L5=LZ+=LI^;Kq%xgeUiLorLu!oqC+lCP7Z`XZ29rgynI>17$Y&*s9KbD(ba; zGp~H~GS6})&5MMZ0{rFuaQSP#dedLXJ;7 z9Mb>w*lZB=0vH34lSioJurUWe9Cw=mxefpbXgOTlZd8z@*w`+E{k>_d=H{dgP#JKl z6=r5uVZp@@;FScZyQ;3|Z_;Y1XijPE=W4F&F$8YXqHv|daZIrHz?{!Z;J)9 zO6|$FVx200qniN})t(g)AuhTYUIHik#))>#xf%*=BQ6yO!HLj7SXSWO_=6Vb4qGn& zjTq@AJp_t3=Qsq1de0YpXy7<#1NWiL|9XdEH@wB}Y3wDbM6lv=wA~~ux&5C8A;60n zFg!4{h>KiJQjzElpL-&KS4>I_E)b#Yo+?E3v2hhLGV_v^~dCJ72RTwZ>TNSe{;p$prYXE+W1BC!$0sf>T|V$hnU?mn?C z;EHR}YOSj3+QTAD+m`9VR1Wls1XKe!Zp);nLi*JgwmTqvT2HpSQWQm&ZD;!>dglV) zJ`bIDeNjo513Xe*)(opr_!PYg3RHn{Jes+m*7M7~A&p|_BmSCipyff-q0059sf0cZ zOpH_4^svfd< zO7x@-u9!{b_iB}>sJGbW;9Ps6B^-a=*Wc2}<0UEcngdqpR{NW>8|dENX0D_JaHjn! zIgKQfw+}vexl@b*y_4X{SdDhoK7<)1XDZt1sq|t*2E^N4WS>PzOPk!n3p01RsvM$R z0oDFttFHyGHDOUQO6%hms#!ru^LYsd{5KU0^`HfQm+481MBLfp839=Ex@8iTx;OA(eba)kHxXkX5(s#aY)dB@eJ55282Nn#5+wVCJK+$6S=* z{UnLleo~7vWpZo+PG3D$@l9Fta}O-ZejReq>mR0qP#ykCF0`?{>E2Jq z!|Z=->MAM zH8!*LgBxRy`uqIE&~-X)-9snxKO;$=9q9XMsKZ+K0(FZ1%Rkph{56rP@1-IDL%fIF z&L}2?cx~uq5wv$5$NxH_wehRl?GwCH%jj;~INQG$m9}jBy_Ebee~D~d*fJ#g|KKgg ziWt6iGIhEO-naxBD9{w#O`p1K5^rNA_;X$Tm%b@@T8nd`0jRjp^X0}ehr#O?v@6pt z;I3SO@dg`y`FZP`=fIZqVN1o497G(=hIN*!_-wGs1C=Y@R3q(Ca4>eMt=cs{HHjZb z!PQJva9+HKZrd%t(52-ntr;BjS-_s(d2-pJ?_may5lbI#Yh(N5>o+b*k*8{;F#>BP zE42Jz5%!%vs4Gg7s8vOIU7!eR2tfHuVoN+8qxUJb%{->x`Y3tz1*#o7nesFxn3~>s zi7>+cND})w!}Y5hi{WUj=Ci|__~y9s#@(y9V$(DJ ziz`m^ntdblFw4fY^Q!rks&ugN&v`G8{Q;=0+#WzO0|NXE`$aH6)E1m-5j7SZ#L>d8 z#XoDj=4RJ9Vt=S%scOFhn^r$!BD^+MQbAhJzM>ziIW!l_(P;1t`14eZbgjVFJjhy! z$Cc-b-N2UFzH!`z71N^_WQg5PmjTm{!sD}(I`-#pyRdQP31%od1sA&HpKXYe{W+O6 z;Nt?P?2zd%JVuLp&}?l51Xp4rmdK2yuOcxPO;-o47f7>=t_n?E)z9&vYvpWIuGaak z$>%SX=revx@R3s%z!^8mMzjX;MDQw(*`KBaRxguD9uTr z*b7<$Hb=`22?UN}pxKVJXPB`ILIzoAMb(h3pVpXb`^V`yyO=fXa;=zD9JV}~no82a z!de(+4fiTv*ot{CHhCMM?(wRit$Blfe^f+s^wLm`4rSX7|YAAu5Y-rC#8hL!! zw&0@6&m*UCQ1sy(Dcgi~ZL@h708}S3+Dq-Db{Wvf8SoCp!XD{?qN~jv{nX*0ts4`0 zBP|GYD-2RWn1DdvcHzFGZP50$fy9!7paS=fXeZ8++nwAy{0bzM%oR|YRXXPGA5hGy zEb@J|Tsjg7a86#91h_EysQq9Kf40yR1ol|(IgY4ZFRm`k;vNxI+S7 z0zV_pa~ttm*Y$g-tf3lXUUzhP^j48??o=d20n5<_!K2f&v`&R?Z%j`f5%=Ctfm`lg zPS+A5*#+{e6mY%H8m4rH!mQfe8`}J3yPde|3NCxuZB=q|*(4J~ZO9h}O;X}XcYwA6 z`jt!;RjhAOc)jJPXSA9OZ6;g}FAwvzV?}ID>B`QRHb-r3_WmzB=Xyt2n8`9); z+bm~pogc(SH-z%^3H&0zk9zKCBR5i3fbYC4hh+>$`|(gZLDCzO`7C6uilJ_Mz!@@Y z=6ZeZ-o+GuFES*(UZJY>30wG*W8$sH6P;vgPWqD4L-{;#8;GrIj#xT}jDOdI@YO?y zdV1!eT>Kd2DODJKL~tC<&czOU>I*NAYo>mPwe$*YEHH`dF~*s_;IZeMxteXhvnFFO z4kdaZ>`4wCJcR4fQ$94y8Kk|@2{7}12CEu^TCE;vJA<> zOxyN}HCJ5$8SQ988f6ha4gOSMj|kqY>EBC0R-eJ}wcrM#lkl$}VwzOB%ZyqIvXn%K z^A9+&plc@`DjJiG0$$zO?ww5Jr`Ax_I?dn^=c(1HwpKueyaM^F*G>H?k2&>qxmxQU z(+haG%u?wo`Nl3%gMWUT|DU2_dyVg9hxH+b@Aiu@`Ji%#?y$pm)#E)-bf4y#JbmmK z?xsH<73Kd0+tN1{JmA)N^_HcL-1Km1p)dgFb|!tJN}A%{Wd1dq<>i-Evp2Cw?2Af< zeQzgV*o@__dO_T@L$ft3z~G8vNU?45=#m5GUeoQCl>~ceHkD7QR-mq0f`QH=|N5%g zshVf|n+~8kj#m8YCM3IDK^nfUG%$OZ75%JHq4XPDL4ji4M@DD$p9>-;MMT(6bQg=MlsIX{U&%Lvo z>7k=Y4H^>&an?VAEH%DmAIvCdIh2{wIZitvyqH2_PL1NyMb;+2#l~6SYtiJWV5;x7 zMiF`ovPQwv#UxqKz%5fsl2;_XfL@z@+V>ITYtV-|xK{XAv=9tKJbdfuWu{y8m)B{W^@Tz&MUfdesgpL0(@VP;raO?K z8^|xMkW@PMH>J}V_mBusk>TDmcRq`BoJ<H;2(la^wsFzS;hBmhr)w+-Kl=)f7V#V>@~slipPQp> ztC2ZR;ngarBz;WfN1hO@GB#0$uS3kD(o$Cl7uSF}Ivnt9UbDphp|)JMhm!tj$SsnH z6E9GF#`ya^l(n4D=@mZJW+bs*^g<+_9mJHlJB+v?TdAm`J{OjnR5i%^o%g9XICHi3 zOGtNZUwnNIMrg)Lgeg8!bmq?@-*y~|YB+S}>QhdK*tYUE&{539dOR#4?7)vD{!MsA zn+rvD@jUC0M3<|L549lsDe31wt_L_4(lR5!jTRD^GA~kC%b`G+ULVe1{~Ukd8=7)^ znh~d_M~|@b7+u%>$KzMH2=roaJP!H?hM#i+1KR;Z`nEWLF*s-oS78x(zO-^8$}n^& z#aZgkU<3zDw;cBEr-jR(N0Ue2*!hfmeo&*;Pr%WJzq<-2u>F#nc^+|HfS*e3VKLq z-yxT0jz53v22q(DJ;H(IK~5^B!w2Nko!kqD8kSEv!*gZUYEl*yt5&D!x?Vg-pg!5i z^<|H_g`ClF|-&*LDiLrj755# z#>uOUN7O_&sPwR#2j}6_$eJQiO1j^gI9a!&UAbawhikYL>Rn_Pt z8g_g9u~NT34ZpRwsi~y%hIymGqb9VmwwMlQl^q~mNbHnt^BPMC~j~w5k6(40W{pG64m8gX<%A@u7HA-c^N>)Ez(~DKSo+BFq@t| zBd~|)Un$jHZ%eVoTOCgR$>!dC1~NkjaA8i~ymOp`T=_1#Em(s58&e`ls$ux~P~y#GHpMPE?> literal 0 HcmV?d00001 diff --git a/swagger-assets/img3.png b/swagger-assets/img3.png new file mode 100644 index 0000000000000000000000000000000000000000..4ea8d9ae1fa1e13a8e1f092b1ca5581a0c9f2db1 GIT binary patch literal 132956 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%0A$IQ!?%& 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 literal 0 HcmV?d00001 From b652f70973f6739e3ec30a61e62794e2951af413 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Wed, 29 May 2024 21:48:54 +0200 Subject: [PATCH 12/15] refs #379. Fixed error UserInput create --- README.md | 12 ++++- config/packages/security.yaml | 2 +- config/routes/api_platform.yaml | 1 - migrations/Version20240529131520.php | 34 ++++++++++++++ src/DataFixtures/AppFixtures.php | 14 +++++- src/Dto/Input/UserInput.php | 9 ++-- src/Entity/OrganizationalUnit.php | 1 + src/Factory/OrganizationalUnitFactory.php | 54 +++++++++++++++++++++++ tests/Functional/UserGroupTest.php | 10 ++--- tests/Functional/UserTest.php | 10 ++--- 10 files changed, 127 insertions(+), 20 deletions(-) create mode 100644 migrations/Version20240529131520.php create mode 100644 src/Factory/OrganizationalUnitFactory.php diff --git a/README.md b/README.md index 0328408..4aeaad1 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,14 @@ ogCore es el servicio central de OpenGnsys, diseñado para proporcionar funcionalidades a través de una API RESTful. Esta herramienta utiliza tecnología PHP, aprovechando el framework Symfony y el ORM Doctrine para gestionar la base de datos. A continuación, se detallan los pasos necesarios para desplegar el proyecto en un entorno de desarrollo. +## Versiones y tecnologías utilizadas + +- PHP 8.3 +- Symfony 6.4 +- Doctrine 2.19 +- API Platform 3.2 +- MariaDB 10.11 + ## Requisitos Antes de comenzar, asegúrate de tener los siguientes requisitos: @@ -46,7 +54,7 @@ Comprobamos, que el contenedor de Nginx, tiene el puerto 8080 levantado correcta acceder a la siguiente URL: ```sh -http://127.0.0.1:8080/api/docs +http://127.0.0.1:8080/docs ``` Si todo ha ido bien, deberiamos ver la documentación de la API de ogCore. @@ -68,7 +76,7 @@ docker exec ogcore-php php bin/console app:load-default-user-groups Api Platform proporciona una interfaz de usuario para interactuar con la API de ogCore. Para acceder a la interfaz de usuario, accede a la siguiente URL: ```sh -http://127.0.0.1:8080/api/docs +http://127.0.0.1:8080/docs ``` Para poder autenticarte, necesitas un token JWT. Para obtenerlo, accedemos al endpoint de autenticación "auth/login": diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 978777f..cf97c8d 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -27,7 +27,7 @@ security: access_control: - { path: ^/$, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI - - { path: ^/api/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI docs + - { path: ^/docs, roles: PUBLIC_ACCESS } # Allows accessing the Swagger UI docs - { path: ^/auth/login, roles: PUBLIC_ACCESS } - { path: ^/auth/refresh, roles: PUBLIC_ACCESS } - { path: ^/, roles: IS_AUTHENTICATED_FULLY } diff --git a/config/routes/api_platform.yaml b/config/routes/api_platform.yaml index 38f11cb..350d2a8 100644 --- a/config/routes/api_platform.yaml +++ b/config/routes/api_platform.yaml @@ -1,4 +1,3 @@ api_platform: resource: . type: api_platform - prefix: /api diff --git a/migrations/Version20240529131520.php b/migrations/Version20240529131520.php new file mode 100644 index 0000000..b596cab --- /dev/null +++ b/migrations/Version20240529131520.php @@ -0,0 +1,34 @@ +addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D727ACA70'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D727ACA70 FOREIGN KEY (parent_id) REFERENCES organizational_unit (id) ON DELETE SET NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE organizational_unit DROP FOREIGN KEY FK_749AEB2D727ACA70'); + $this->addSql('ALTER TABLE organizational_unit ADD CONSTRAINT FK_749AEB2D727ACA70 FOREIGN KEY (parent_id) REFERENCES organizational_unit (id)'); + } +} diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 8feb58e..3f0d310 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -2,6 +2,8 @@ namespace App\DataFixtures; +use App\Entity\OrganizationalUnit; +use App\Factory\OrganizationalUnitFactory; use App\Factory\UserFactory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; @@ -10,12 +12,22 @@ class AppFixtures extends Fixture { CONST ADMIN_USER = 'ogadmin'; - /** * @throws \Exception */ public function load(ObjectManager $manager): void { UserFactory::createOne(['username' => self::ADMIN_USER]); + $rootUnit = OrganizationalUnitFactory::createOne(['name' => 'Centro de Computación', 'parent' => null]); + + $roomUnit = OrganizationalUnitFactory::createOne([ + 'name' => 'Aula 1', + 'parent' => $rootUnit + ]); + + OrganizationalUnitFactory::createOne([ + 'name' => 'Aula 2', + 'parent' => $roomUnit + ]); } } diff --git a/src/Dto/Input/UserInput.php b/src/Dto/Input/UserInput.php index 79394c9..97b1230 100644 --- a/src/Dto/Input/UserInput.php +++ b/src/Dto/Input/UserInput.php @@ -2,6 +2,8 @@ namespace App\Dto\Input; +use ApiPlatform\Metadata\ApiProperty; +use App\Dto\Output\UserGroupOutput; use App\Entity\OrganizationalUnit; use App\Entity\User; use App\Entity\UserGroup; @@ -14,13 +16,11 @@ final class UserInput #[Groups(['user:write'])] public ?string $username = null; - #[Groups(['user:write'])] - public array $roles = []; - /** * @var OrganizationalUnit[] */ #[Groups(['user:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] public array $allowedOrganizationalUnits = []; #[Assert\NotBlank(groups: ['user:post'])] @@ -36,6 +36,7 @@ final class UserInput * @var UserGroup[] */ #[Groups(['user:write'])] + #[ApiProperty(readableLink: false, writableLink: false)] public array $userGroups = []; public function __construct(?User $user = null) @@ -45,7 +46,6 @@ final class UserInput } $this->username = $user->getUsername(); - $this->roles = $user->getRoles(); $this->enabled= $user->isEnabled(); $this->userGroups = $user->getUserGroups()->toArray(); $this->allowedOrganizationalUnits = $user->getAllowedOrganizationalUnits()->toArray(); @@ -58,7 +58,6 @@ final class UserInput } $user->setUsername($this->username); - $user->setRoles($this->roles); $user->setEnabled($this->enabled); foreach ($this->userGroups as $userGroup) { diff --git a/src/Entity/OrganizationalUnit.php b/src/Entity/OrganizationalUnit.php index 014c847..6e58c7f 100644 --- a/src/Entity/OrganizationalUnit.php +++ b/src/Entity/OrganizationalUnit.php @@ -35,6 +35,7 @@ class OrganizationalUnit extends AbstractEntity #[Gedmo\TreeParent] #[ORM\ManyToOne(targetEntity: self::class, inversedBy: 'organizationalUnits')] + #[ORM\JoinColumn( onDelete: 'SET NULL')] private ?self $parent = null; /** diff --git a/src/Factory/OrganizationalUnitFactory.php b/src/Factory/OrganizationalUnitFactory.php new file mode 100644 index 0000000..c4be743 --- /dev/null +++ b/src/Factory/OrganizationalUnitFactory.php @@ -0,0 +1,54 @@ + + */ +final class OrganizationalUnitFactory extends ModelFactory +{ + /** + * @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services + * + * @todo inject services if required + */ + public function __construct() + { + parent::__construct(); + } + + /** + * @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(), + '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(OrganizationalUnit $organizationalUnit): void {}) + ; + } + + protected static function getClass(): string + { + return OrganizationalUnit::class; + } +} diff --git a/tests/Functional/UserGroupTest.php b/tests/Functional/UserGroupTest.php index 5806634..0e5572c 100644 --- a/tests/Functional/UserGroupTest.php +++ b/tests/Functional/UserGroupTest.php @@ -38,12 +38,12 @@ class UserGroupTest extends AbstractTest UserGroupFactory::createOne(['name' => 'Operador de aulas', 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_OPERATOR'], 'enabled' => true]); UserGroupFactory::createOne(['name' => 'Usuario', 'permissions' => ['ROLE_USER'], 'enabled' => true]); - $this->createClientWithCredentials()->request('GET', '/api/user-groups'); + $this->createClientWithCredentials()->request('GET', '/user-groups'); $this->assertResponseStatusCodeSame(Response::HTTP_OK); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ - '@context' => '/api/contexts/UserGroup', - '@id' => '/api/user-groups', + '@context' => '/contexts/UserGroup', + '@id' => '/user-groups', '@type' => 'hydra:Collection', 'hydra:totalItems' => 4, ]); @@ -59,7 +59,7 @@ class UserGroupTest extends AbstractTest public function testCreateUserGroup(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); - $this->createClientWithCredentials()->request('POST', '/api/user-groups',['json' => [ + $this->createClientWithCredentials()->request('POST', '/user-groups',['json' => [ 'name' => self::USER_GROUP_CREATE, 'enabled' => true, ]]); @@ -67,7 +67,7 @@ class UserGroupTest extends AbstractTest $this->assertResponseStatusCodeSame(201); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ - '@context' => '/api/contexts/UserGroupOutput', + '@context' => '/contexts/UserGroupOutput', '@type' => 'UserGroup', 'name' => self::USER_GROUP_CREATE, 'enabled' => true, diff --git a/tests/Functional/UserTest.php b/tests/Functional/UserTest.php index b0e9cc4..a63d626 100644 --- a/tests/Functional/UserTest.php +++ b/tests/Functional/UserTest.php @@ -30,12 +30,12 @@ class UserTest extends AbstractTest UserFactory::createOne(['username' => self::USER_ADMIN]); UserFactory::createMany(10); - $this->createClientWithCredentials()->request('GET', '/api/users'); + $this->createClientWithCredentials()->request('GET', '/users'); $this->assertResponseStatusCodeSame(Response::HTTP_OK); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ - '@context' => '/api/contexts/User', - '@id' => '/api/users', + '@context' => '/contexts/User', + '@id' => '/users', '@type' => 'hydra:Collection', 'hydra:totalItems' => 11, ]); @@ -51,7 +51,7 @@ class UserTest extends AbstractTest public function testCreateUser(): void { UserFactory::createOne(['username' => self::USER_ADMIN]); - $this->createClientWithCredentials()->request('POST', '/api/users',['json' => [ + $this->createClientWithCredentials()->request('POST', '/users',['json' => [ 'username' => self::USER_CREATE, 'password' => '12345678', 'enabled' => true, @@ -60,7 +60,7 @@ class UserTest extends AbstractTest $this->assertResponseStatusCodeSame(201); $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); $this->assertJsonContains([ - '@context' => '/api/contexts/UserOutput', + '@context' => '/contexts/UserOutput', '@type' => 'User', 'username' => self::USER_CREATE, 'enabled' => true, From 8d3c3c195c86a664b2afc707a201ca1003cfa5f3 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 30 May 2024 09:12:10 +0200 Subject: [PATCH 13/15] refs #379. Added userGroupPermission validation --- src/Command/LoadDefaultUserGroupsCommand.php | 11 +++--- src/Dto/Input/UserGroupInput.php | 2 ++ src/Model/UserGroupPermissions.php | 36 +++++++++++++++++++ .../Constraints/UserGroupsValidPermission.php | 26 ++++++++++++++ .../UserGroupsValidPermissionValidator.php | 23 ++++++++++++ 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 src/Model/UserGroupPermissions.php create mode 100644 src/Validator/Constraints/UserGroupsValidPermission.php create mode 100644 src/Validator/Constraints/UserGroupsValidPermissionValidator.php diff --git a/src/Command/LoadDefaultUserGroupsCommand.php b/src/Command/LoadDefaultUserGroupsCommand.php index a4bdda2..51bc6fe 100644 --- a/src/Command/LoadDefaultUserGroupsCommand.php +++ b/src/Command/LoadDefaultUserGroupsCommand.php @@ -3,6 +3,7 @@ namespace App\Command; use App\Entity\UserGroup; +use App\Model\UserGroupPermissions; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -22,22 +23,22 @@ class LoadDefaultUserGroupsCommand extends Command $userGroups = [ [ 'name' => 'Super Admin', - 'permissions' => ['ROLE_SUPER_ADMIN'], + 'permissions' => [UserGroupPermissions::ROLE_SUPER_ADMIN], 'enabled' => true ], [ 'name' => 'Administrador de aulas', - 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_ADMIN'], + 'permissions' => [UserGroupPermissions::ROLE_ORGANIZATIONAL_UNIT_ADMIN], 'enabled' => true ], [ 'name' => 'Operador de aulas', - 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_OPERATOR'], + 'permissions' => [UserGroupPermissions::ROLE_ORGANIZATIONAL_UNIT_OPERATOR], 'enabled' => true ], [ - 'name' => 'Usuario', - 'permissions' => ['ROLE_USER'], + 'name' => 'Usuario básico de aulas', + 'permissions' => [UserGroupPermissions::ROLE_ORGANIZATIONAL_UNIT_MINIMAL], 'enabled' => true ], ]; diff --git a/src/Dto/Input/UserGroupInput.php b/src/Dto/Input/UserGroupInput.php index 71047cf..918e1ef 100644 --- a/src/Dto/Input/UserGroupInput.php +++ b/src/Dto/Input/UserGroupInput.php @@ -3,6 +3,7 @@ namespace App\Dto\Input; use App\Entity\UserGroup; +use App\Validator\Constraints\UserGroupsValidPermission; use Symfony\Component\Serializer\Annotation\Groups; use Symfony\Component\Validator\Constraints as Assert; @@ -13,6 +14,7 @@ final class UserGroupInput public ?string $name = null; #[Groups(['user-group:write'])] + #[UserGroupsValidPermission] public ?array $permissions = []; #[Assert\NotNull] diff --git a/src/Model/UserGroupPermissions.php b/src/Model/UserGroupPermissions.php new file mode 100644 index 0000000..90a3c9b --- /dev/null +++ b/src/Model/UserGroupPermissions.php @@ -0,0 +1,36 @@ + 'Super Admin', + self::ROLE_ORGANIZATIONAL_UNIT_ADMIN => 'Admin de aulas', + self::ROLE_ORGANIZATIONAL_UNIT_OPERATOR => 'Operador de aulas', + self::ROLE_ORGANIZATIONAL_UNIT_MINIMAL => 'Usuario básico de aulas', + self::ROLE_USER => 'Usuario', + ]; + + public static function getRoleNames(): array + { + return self::ROLE_NAMES; + } + + public static function getRoleName(string $role): ?string + { + return self::ROLE_NAMES[$role] ?? null; + } + + public static function getRoles(): array + { + return array_keys(self::ROLE_NAMES); + } +} \ No newline at end of file diff --git a/src/Validator/Constraints/UserGroupsValidPermission.php b/src/Validator/Constraints/UserGroupsValidPermission.php new file mode 100644 index 0000000..b4a2841 --- /dev/null +++ b/src/Validator/Constraints/UserGroupsValidPermission.php @@ -0,0 +1,26 @@ +roles = UserGroupPermissions::getRoles(); + $this->message = sprintf( + 'The permission is not valid. Please use one of the following: %s', + implode(', ', $this->roles) + ); + } + + +} \ No newline at end of file diff --git a/src/Validator/Constraints/UserGroupsValidPermissionValidator.php b/src/Validator/Constraints/UserGroupsValidPermissionValidator.php new file mode 100644 index 0000000..031039a --- /dev/null +++ b/src/Validator/Constraints/UserGroupsValidPermissionValidator.php @@ -0,0 +1,23 @@ +context->buildViolation($constraint->message)->addViolation(); + } + } + } +} \ No newline at end of file From e1d782c8a0371ffe3e5d066b8c6d048dffed8ed2 Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 30 May 2024 11:43:48 +0200 Subject: [PATCH 14/15] refs #379. Allow_extra_attributes false --- config/api_platform/User.yaml | 1 + config/packages/api_platform.yaml | 2 ++ src/State/Processor/UserProcessor.php | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index 427c416..d82f4c2 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -7,6 +7,7 @@ resources: groups: ['default', 'user:read'] denormalization_context: groups: ['user:write'] + operations: ApiPlatform\Metadata\GetCollection: provider: App\State\Provider\UserProvider diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 8fad293..ee2c7ec 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -11,6 +11,8 @@ api_platform: paths: ['%kernel.project_dir%/config/api_platform', '%kernel.project_dir%/src/Dto'] defaults: pagination_client_items_per_page: true + denormalization_context: + allow_extra_attributes: false cache_headers: vary: [ 'Content-Type', 'Authorization', 'Origin' ] extra_properties: diff --git a/src/State/Processor/UserProcessor.php b/src/State/Processor/UserProcessor.php index fec80b5..bf8558b 100644 --- a/src/State/Processor/UserProcessor.php +++ b/src/State/Processor/UserProcessor.php @@ -48,12 +48,12 @@ class UserProcessor implements ProcessorInterface throw new \Exception(sprintf('data is not instance of %s', UserInput::class)); } - $entity = null; + $user = null; if (isset($uriVariables['uuid'])) { - $entity = $this->userRepository->findOneByUuid($uriVariables['uuid']); + $user = $this->userRepository->findOneByUuid($uriVariables['uuid']); } - $user = $data->createOrUpdateEntity($entity); + $user = $data->createOrUpdateEntity($user); if ($data->password !== null){ $user->setPassword($this->userPasswordHasher->hashPassword($user, $data->password)); From d13b31ca8904f9516e256d8b8a03b56c9f9b3f2b Mon Sep 17 00:00:00 2001 From: Manuel Aranda Date: Thu, 30 May 2024 12:45:33 +0200 Subject: [PATCH 15/15] refs #379. Security user, userGroups endpoints --- config/api_platform/User.yaml | 1 + config/api_platform/UserGroup.yaml | 1 + src/DataFixtures/AppFixtures.php | 3 ++- tests/Functional/UserGroupTest.php | 9 +++++---- tests/Functional/UserTest.php | 13 +++++++------ 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/config/api_platform/User.yaml b/config/api_platform/User.yaml index d82f4c2..efe4597 100644 --- a/config/api_platform/User.yaml +++ b/config/api_platform/User.yaml @@ -1,5 +1,6 @@ resources: App\Entity\User: + security: 'is_granted("ROLE_SUPER_ADMIN")' input: App\Dto\Input\UserInput output: App\Dto\Output\UserOutput processor: App\State\Processor\UserProcessor diff --git a/config/api_platform/UserGroup.yaml b/config/api_platform/UserGroup.yaml index 0e25cb8..0f2a80b 100644 --- a/config/api_platform/UserGroup.yaml +++ b/config/api_platform/UserGroup.yaml @@ -1,5 +1,6 @@ resources: App\Entity\UserGroup: + security: 'is_granted("ROLE_SUPER_ADMIN")' processor: App\State\Processor\UserGroupProcessor input: App\Dto\Input\UserGroupInput output: App\Dto\Output\UserGroupOutput diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 3f0d310..f2b8986 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -5,6 +5,7 @@ namespace App\DataFixtures; use App\Entity\OrganizationalUnit; use App\Factory\OrganizationalUnitFactory; use App\Factory\UserFactory; +use App\Model\UserGroupPermissions; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; @@ -17,7 +18,7 @@ class AppFixtures extends Fixture */ public function load(ObjectManager $manager): void { - UserFactory::createOne(['username' => self::ADMIN_USER]); + UserFactory::createOne(['username' => self::ADMIN_USER, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); $rootUnit = OrganizationalUnitFactory::createOne(['name' => 'Centro de Computación', 'parent' => null]); $roomUnit = OrganizationalUnitFactory::createOne([ diff --git a/tests/Functional/UserGroupTest.php b/tests/Functional/UserGroupTest.php index 0e5572c..257360e 100644 --- a/tests/Functional/UserGroupTest.php +++ b/tests/Functional/UserGroupTest.php @@ -6,6 +6,7 @@ use App\Entity\User; use App\Entity\UserGroup; use App\Factory\UserFactory; use App\Factory\UserGroupFactory; +use App\Model\UserGroupPermissions; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -31,7 +32,7 @@ class UserGroupTest extends AbstractTest */ public function testGetCollectionUserGroup(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); UserGroupFactory::createOne(['name' => 'Super Admin', 'permissions' => ['ROLE_SUPER_ADMIN'], 'enabled' => true]); UserGroupFactory::createOne(['name' => 'Administrador de aulas', 'permissions' => ['ROLE_ORGANIZATIONAL_UNIT_ADMIN'], 'enabled' => true]); @@ -58,7 +59,7 @@ class UserGroupTest extends AbstractTest */ public function testCreateUserGroup(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); $this->createClientWithCredentials()->request('POST', '/user-groups',['json' => [ 'name' => self::USER_GROUP_CREATE, 'enabled' => true, @@ -83,7 +84,7 @@ class UserGroupTest extends AbstractTest */ public function testUpdateUserGroup(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); UserGroupFactory::createOne(['name' => self::USER_GROUP_UPDATE]); $iri = $this->findIriBy(UserGroup::class, ['name' => self::USER_GROUP_UPDATE]); @@ -111,7 +112,7 @@ class UserGroupTest extends AbstractTest */ public function testDeleteUser(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); UserGroupFactory::createOne(['name' => self::USER_GROUP_DELETE]); $iri = $this->findIriBy(UserGroup::class, ['name' => self::USER_GROUP_DELETE]); diff --git a/tests/Functional/UserTest.php b/tests/Functional/UserTest.php index a63d626..6618ba0 100644 --- a/tests/Functional/UserTest.php +++ b/tests/Functional/UserTest.php @@ -4,6 +4,7 @@ namespace Functional; use App\Entity\User; use App\Factory\UserFactory; +use App\Model\UserGroupPermissions; use Symfony\Component\HttpFoundation\Response; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; @@ -27,7 +28,7 @@ class UserTest extends AbstractTest */ public function testGetCollectionUser(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); UserFactory::createMany(10); $this->createClientWithCredentials()->request('GET', '/users'); @@ -50,7 +51,7 @@ class UserTest extends AbstractTest */ public function testCreateUser(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); $this->createClientWithCredentials()->request('POST', '/users',['json' => [ 'username' => self::USER_CREATE, 'password' => '12345678', @@ -76,8 +77,8 @@ class UserTest extends AbstractTest */ public function testUpdateUser(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); - UserFactory::createOne(['username' => self::USER_UPDATE]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + UserFactory::createOne(['username' => self::USER_UPDATE, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); $iri = $this->findIriBy(User::class, ['username' => self::USER_UPDATE]); @@ -101,8 +102,8 @@ class UserTest extends AbstractTest */ public function testDeleteUser(): void { - UserFactory::createOne(['username' => self::USER_ADMIN]); - UserFactory::createOne(['username' => self::USER_DELETE]); + UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); + UserFactory::createOne(['username' => self::USER_DELETE, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]); $iri = $this->findIriBy(User::class, ['username' => self::USER_DELETE]);