diff --git a/README.md b/README.md index bb8d7712..c1f38b5d 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,17 @@ Documentation is available at: https://docs.dotkernel.org/api-documentation/ ## Badges ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/api) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/api/5.2.0) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/api/6.0.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/api)](https://github.com/dotkernel/api/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/api)](https://github.com/dotkernel/api/network) [![GitHub stars](https://img.shields.io/github/stars/dotkernel/api)](https://github.com/dotkernel/api/stargazers) -[![GitHub license](https://img.shields.io/github/license/dotkernel/api)](https://github.com/dotkernel/api/blob/5.0/LICENSE.md) +[![GitHub license](https://img.shields.io/github/license/dotkernel/api)](https://github.com/dotkernel/api/blob/6.0/LICENSE.md) -[![Build Static](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml/badge.svg?branch=5.0)](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml) +[![Build Static](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml/badge.svg?branch=6.0)](https://github.com/dotkernel/api/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/dotkernel/api/graph/badge.svg?token=53FN78G5CK)](https://codecov.io/gh/dotkernel/api) -[![Qodana](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml/badge.svg?branch=5.0)](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml) -[![PHPStan](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml/badge.svg?branch=5.0)](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml) +[![Qodana](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml/badge.svg?branch=6.0)](https://github.com/dotkernel/api/actions/workflows/qodana_code_quality.yml) +[![PHPStan](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml/badge.svg?branch=6.0)](https://github.com/dotkernel/api/actions/workflows/static-analysis.yml) ## Getting Started @@ -77,7 +77,7 @@ composer development-status * run the database migrations by using the following command: ```shell -php vendor/bin/doctrine-migrations migrate +php ./vendor/bin/doctrine-migrations migrate ``` This command will prompt you to confirm that you want to run it: @@ -93,7 +93,7 @@ Hit `Enter` to confirm the operation. To list all the fixtures, run: ```shell -php bin/doctrine fixtures:list +php ./bin/doctrine fixtures:list ``` This will output all the fixtures in the order of execution. @@ -101,13 +101,13 @@ This will output all the fixtures in the order of execution. To execute all fixtures, run: ```shell -php bin/doctrine fixtures:execute +php ./bin/doctrine fixtures:execute ``` To execute a specific fixture, run: ```shell -php bin/doctrine fixtures:execute --class=FixtureClassName +php ./bin/doctrine fixtures:execute --class=FixtureClassName ``` More details on how fixtures work can be found here: https://github.com/dotkernel/dot-data-fixtures#creating-fixtures diff --git a/composer.json b/composer.json index 2c093e32..0472dba8 100644 --- a/composer.json +++ b/composer.json @@ -57,8 +57,9 @@ "dotkernel/dot-dependency-injection": "^1.2", "dotkernel/dot-errorhandler": "^4.0.0", "dotkernel/dot-mail": "^5.3.0", - "dotkernel/dot-response-header": "^3.4.1", - "dotkernel/dot-router": "^1.0.4", + "dotkernel/dot-response-header": "^3.5.0", + "dotkernel/dot-router": "^1.0.5", + "laminas/laminas-authentication": "^2.18", "laminas/laminas-component-installer": "^3.5.0", "laminas/laminas-config-aggregator": "^1.18.0", "laminas/laminas-hydrator": "^4.16.0", @@ -70,21 +71,21 @@ "mezzio/mezzio-authorization-rbac": "^1.8.0", "mezzio/mezzio-cors": "^1.13.0", "mezzio/mezzio-fastroute": "^3.12.0", - "mezzio/mezzio-hal": "^2.10", + "mezzio/mezzio-hal": "^2.10.1", "mezzio/mezzio-problem-details": "^1.15.0", "mezzio/mezzio-twigrenderer": "^2.17.0", "ramsey/uuid-doctrine": "^2.1.0", "roave/psr-container-doctrine": "^5.2.2", "symfony/filesystem": "^7.2.0", - "zircote/swagger-php": "^5.0.6" + "zircote/swagger-php": "^5.0.7" }, "require-dev": { "laminas/laminas-coding-standard": "^3.0.1", "laminas/laminas-development-mode": "^3.13.0", "mezzio/mezzio-tooling": "^2.10.1", - "phpstan/phpstan": "^2.1.7", + "phpstan/phpstan": "^2.1.11", "phpstan/phpstan-doctrine": "^2.0.2", - "phpstan/phpstan-phpunit": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.6", "phpunit/phpunit": "^10.5.45", "roave/security-advisories": "dev-latest", "symfony/var-dumper": "^7.2.3" @@ -98,6 +99,7 @@ "Core\\Admin\\": "src/Core/src/Admin/src/", "Core\\App\\": "src/Core/src/App/src/", "Core\\Security\\": "src/Core/src/Security/src/", + "Core\\Setting\\": "src/Core/src/Setting/src/", "Core\\User\\": "src/Core/src/User/src/" } }, diff --git a/config/autoload/authorization.global.php b/config/autoload/authorization.global.php index 0200936f..06945bf0 100644 --- a/config/autoload/authorization.global.php +++ b/config/autoload/authorization.global.php @@ -2,8 +2,8 @@ declare(strict_types=1); -use Core\Admin\Entity\AdminRole; -use Core\User\Entity\UserRole; +use Core\Admin\Enum\AdminRoleEnum; +use Core\User\Enum\UserRoleEnum; return [ /** @@ -19,17 +19,17 @@ */ 'mezzio-authorization-rbac' => [ 'roles' => [ - AdminRole::ROLE_SUPERUSER => [], - AdminRole::ROLE_ADMIN => [ - AdminRole::ROLE_SUPERUSER, + AdminRoleEnum::Superuser->value => [], + AdminRoleEnum::Admin->value => [ + AdminRoleEnum::Superuser->value, ], - UserRole::ROLE_GUEST => [ - UserRole::ROLE_USER, + UserRoleEnum::Guest->value => [ + UserRoleEnum::User->value, ], ], 'permissions' => [ - AdminRole::ROLE_SUPERUSER => [], - AdminRole::ROLE_ADMIN => [ + AdminRoleEnum::Superuser->value => [], + AdminRoleEnum::Admin->value => [ 'admin::list-admin', 'admin::create-admin', 'admin::delete-admin', @@ -54,7 +54,7 @@ 'app::create-error-report', 'app::view-index', ], - UserRole::ROLE_USER => [ + UserRoleEnum::User->value => [ 'user::delete-account', 'user::view-account', 'user::update-account', @@ -62,7 +62,7 @@ 'user::view-account-avatar', 'user::create-account-avatar', ], - UserRole::ROLE_GUEST => [ + UserRoleEnum::Guest->value => [ 'app::create-error-report', 'app::view-index', 'user::activate-account', diff --git a/config/autoload/cli.global.php b/config/autoload/cli.global.php index 16a9b9ca..1c91bd3c 100644 --- a/config/autoload/cli.global.php +++ b/config/autoload/cli.global.php @@ -3,9 +3,8 @@ declare(strict_types=1); use Api\Admin\Command\AdminCreateCommand; -use Api\App\Command\RouteListCommand; use Api\App\Command\TokenGenerateCommand; -use Dot\Cli\Command\DemoCommand; +use Core\App\Command\RouteListCommand; use Dot\Cli\FileLockerInterface; return [ @@ -13,7 +12,6 @@ 'version' => '1.0.0', 'name' => 'Dotkernel CLI', 'commands' => [ - DemoCommand::getDefaultName() => DemoCommand::class, RouteListCommand::getDefaultName() => RouteListCommand::class, AdminCreateCommand::getDefaultName() => AdminCreateCommand::class, TokenGenerateCommand::getDefaultName() => TokenGenerateCommand::class, diff --git a/config/autoload/content-negotiation.global.php b/config/autoload/content-negotiation.global.php index b464f768..59a2d6dc 100644 --- a/config/autoload/content-negotiation.global.php +++ b/config/autoload/content-negotiation.global.php @@ -4,7 +4,7 @@ return [ 'content-negotiation' => [ - 'default' => [ // default to any route if not configured above + 'default' => [ // default to any route if not configured above 'Accept' => [ // the Accept is what format the server can send back 'application/json', 'application/hal+json', @@ -14,11 +14,11 @@ 'application/hal+json', ], ], - 'your.route.name' => [ + 'your.route.name' => [ 'Accept' => [], 'Content-Type' => [], ], - 'user.avatar.create' => [ + 'user::create-account-avatar' => [ 'Accept' => [ 'application/json', 'application/hal+json', @@ -27,7 +27,7 @@ 'multipart/form-data', ], ], - 'user.my-avatar.create' => [ + 'user::create-user-avatar' => [ 'Accept' => [ 'application/json', 'application/hal+json', diff --git a/config/autoload/local.php.dist b/config/autoload/local.php.dist index 59221147..ae3ea737 100644 --- a/config/autoload/local.php.dist +++ b/config/autoload/local.php.dist @@ -7,7 +7,7 @@ $baseUrl = 'http://localhost:8080'; $databases = [ 'default' => [ 'host' => 'localhost', - 'dbname' => '', + 'dbname' => 'dotkernel', 'user' => '', 'password' => '', 'port' => 3306, @@ -15,12 +15,13 @@ $databases = [ 'charset' => 'utf8mb4', 'collate' => 'utf8mb4_general_ci', ], - // you can add more database connections into this array + // you can add more database connections to this array ]; return [ 'application' => [ 'name' => 'Dotkernel API', + 'version' => 6, 'url' => $baseUrl, 'versioning' => [ 'documentation_url' => 'https://docs.dotkernel.org/api-documentation/v5/core-features/versioning', @@ -28,14 +29,14 @@ return [ ], 'authentication' => [ 'private_key' => [ - 'key_or_path' => getcwd() . '/data/oauth/private.key', + 'key_or_path' => realpath(__DIR__ . '/../../data/oauth/private.key'), 'key_permissions_check' => false, ], 'public_key' => [ - 'key_or_path' => getcwd() . '/data/oauth/public.key', + 'key_or_path' => realpath(__DIR__ . '/../../data/oauth/public.key'), 'key_permissions_check' => false, ], - 'encryption_key' => require getcwd() . '/data/oauth/encryption.key', + 'encryption_key' => require realpath(__DIR__ . '/../../data/oauth/encryption.key'), 'access_token_expire' => 'P1D', 'refresh_token_expire' => 'P1M', 'auth_code_expire' => 'PT10M', diff --git a/config/config.php b/config/config.php index ebd973a7..2634df46 100644 --- a/config/config.php +++ b/config/config.php @@ -10,6 +10,15 @@ ]; $aggregator = new Laminas\ConfigAggregator\ConfigAggregator([ + // Laminas packages + Laminas\Diactoros\ConfigProvider::class, + Laminas\InputFilter\ConfigProvider::class, + Laminas\Filter\ConfigProvider::class, + Laminas\HttpHandlerRunner\ConfigProvider::class, + Laminas\Hydrator\ConfigProvider::class, + Laminas\Validator\ConfigProvider::class, + + // Mezzio packages Mezzio\Authorization\ConfigProvider::class, Mezzio\Authorization\Acl\ConfigProvider::class, Mezzio\Authorization\Rbac\ConfigProvider::class, @@ -20,14 +29,6 @@ Mezzio\ProblemDetails\ConfigProvider::class, Mezzio\Router\FastRouteRouter\ConfigProvider::class, Mezzio\Twig\ConfigProvider::class, - Laminas\Diactoros\ConfigProvider::class, - Laminas\InputFilter\ConfigProvider::class, - Laminas\Filter\ConfigProvider::class, - Laminas\HttpHandlerRunner\ConfigProvider::class, - Laminas\Hydrator\ConfigProvider::class, - Laminas\Validator\ConfigProvider::class, - // Include cache configuration - new Laminas\ConfigAggregator\ArrayProvider($cacheConfig), Mezzio\Helper\ConfigProvider::class, Mezzio\ConfigProvider::class, Mezzio\Router\ConfigProvider::class, @@ -37,6 +38,9 @@ class_exists(Mezzio\Tooling\ConfigProvider::class) return []; }, + // Include cache configuration + new Laminas\ConfigAggregator\ArrayProvider($cacheConfig), + // DK packages Dot\Cli\ConfigProvider::class, Dot\Log\ConfigProvider::class, @@ -48,15 +52,16 @@ class_exists(Mezzio\Tooling\ConfigProvider::class) Dot\Cache\ConfigProvider::class, Dot\Router\ConfigProvider::class, - // Default App module config - Core\Admin\ConfigProvider::class, - Core\App\ConfigProvider::class, - Core\Security\ConfigProvider::class, - Core\User\ConfigProvider::class, + // Dotkernel modules Api\Admin\ConfigProvider::class, Api\App\ConfigProvider::class, Api\Security\ConfigProvider::class, Api\User\ConfigProvider::class, + Core\Admin\ConfigProvider::class, + Core\App\ConfigProvider::class, + Core\Security\ConfigProvider::class, + Core\Setting\ConfigProvider::class, + Core\User\ConfigProvider::class, // Load application config in a pre-defined order in such a way that local settings // overwrite global settings. (Loaded as first to last): @@ -64,6 +69,7 @@ class_exists(Mezzio\Tooling\ConfigProvider::class) // - `*.global.php` // - `local.php` // - `*.local.php` + // - `local.test.php` new Laminas\ConfigAggregator\PhpFileProvider( realpath(__DIR__) . '/autoload/{{,*.}global,{,*.}local,{,*.}test}.php' ), diff --git a/documentation/Dotkernel_API.postman_collection.json b/documentation/Dotkernel_API.postman_collection.json index 094dd83e..26901446 100644 --- a/documentation/Dotkernel_API.postman_collection.json +++ b/documentation/Dotkernel_API.postman_collection.json @@ -1,10 +1,10 @@ { "info": { - "_postman_id": "aae441d2-e81b-462a-97c1-4541564a9894", + "_postman_id": "f837d47b-cc12-4897-8b1a-ecd26b4bbe44", "name": "Dotkernel_API", "description": "Dotkernel API documentation.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "13971428" + "_exporter_id": "3494496" }, "item": [ { @@ -61,7 +61,7 @@ "response": [] } ], - "description": "Admins manager their accounts." + "description": "Admins manage their own account." }, { "name": "Admin", @@ -73,7 +73,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"identity\": \"{{$randomUserName}}\",\r\n \"password\": \"dotkernel\",\r\n \"passwordConfirm\": \"dotkernel\",\r\n \"firstName\": \"{{$randomFirstName}}\",\r\n \"lastName\": \"{{$randomLastName}}\",\r\n \"roles\": [\r\n {\r\n \"uuid\": \"{{$randomUUID}}\"\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"identity\": \"{{$randomUserName}}\",\r\n \"password\": \"dotkernel\",\r\n \"passwordConfirm\": \"dotkernel\",\r\n \"firstName\": \"{{$randomFirstName}}\",\r\n \"lastName\": \"{{$randomLastName}}\",\r\n \"status\": \"active\",\r\n \"roles\": [\r\n {\r\n \"uuid\": \"{{$randomUUID}}\"\r\n }\r\n ]\r\n}", "options": { "raw": { "language": "json" @@ -113,7 +113,7 @@ "response": [] }, { - "name": "List admin", + "name": "List admins", "request": { "method": "GET", "header": [], @@ -137,7 +137,7 @@ "disabled": true }, { - "key": "order", + "key": "sort", "value": "admin.created", "disabled": true }, @@ -145,6 +145,21 @@ "key": "dir", "value": "desc", "disabled": true + }, + { + "key": "filters[identity]", + "value": "", + "disabled": true + }, + { + "key": "filters[status]", + "value": "", + "disabled": true + }, + { + "key": "filters[role]", + "value": "", + "disabled": true } ] }, @@ -159,7 +174,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"password\": \"dotkernel\",\r\n \"passwordConfirm\": \"dotkernel\",\r\n \"firstName\": \"{{$randomFirstName}}\",\r\n \"lastName\": \"{{$randomLastName}}\",\r\n \"roles\": [\r\n {\r\n \"uuid\": \"{{$randomUUID}}\"\r\n }\r\n ]\r\n}", + "raw": "{\r\n \"password\": \"dotkernel\",\r\n \"passwordConfirm\": \"dotkernel\",\r\n \"firstName\": \"{{$randomFirstName}}\",\r\n \"lastName\": \"{{$randomLastName}}\",\r\n \"roles\": [\r\n {\r\n \"uuid\": \"{{$randomUUID}}\"\r\n }\r\n ],\r\n \"status\": \"active\"\r\n}", "options": { "raw": { "language": "json" @@ -167,13 +182,13 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/admin/{{$randomUUID}}", + "raw": "{{APPLICATION_URL}}/admin/abd444ed-cc65-41e7-96c7-2391a9103f15", "host": [ "{{APPLICATION_URL}}" ], "path": [ "admin", - "{{$randomUUID}}" + "abd444ed-cc65-41e7-96c7-2391a9103f15" ] }, "description": "Admin updates admin account.\n\nReplace random UUID in URL with a valid user UUID." @@ -223,7 +238,8 @@ }, "response": [] } - ] + ], + "description": "Admins manage other admin accounts." }, { "name": "Role", @@ -254,7 +270,7 @@ "disabled": true }, { - "key": "order", + "key": "sort", "value": "role.created", "disabled": true }, @@ -262,6 +278,11 @@ "key": "dir", "value": "desc", "disabled": true + }, + { + "key": "filters[name]", + "value": "", + "disabled": true } ] }, @@ -289,7 +310,8 @@ }, "response": [] } - ] + ], + "description": "Admins manage admin roles." }, { "name": "Security", @@ -323,7 +345,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\r\n \"grant_type\": \"password\",\r\n \"client_id\": \"admin\",\r\n \"client_secret\": \"admin\",\r\n \"scope\": \"api\",\r\n \"username\": \"admin\",\r\n \"password\": \"dotkernel\"\r\n}", + "raw": "{\r\n \"grant_type\": \"password\",\r\n \"client_id\": \"admin\",\r\n \"client_secret\": \"admin\",\r\n \"scope\": \"api\",\r\n \"username\": \"admin\",\r\n \"password\": \"dotadmin\"\r\n}", "options": { "raw": { "language": "json" @@ -395,6 +417,7 @@ "response": [] } ], + "description": "Admins re/generate their access token.", "auth": { "type": "noauth" }, @@ -428,6 +451,7 @@ ] } ], + "description": "Admin module documentation.", "event": [ { "listen": "prerequest", @@ -528,7 +552,8 @@ }, "response": [] } - ] + ], + "description": "Users manage their own avatar." }, { "name": "Activate my account", @@ -539,11 +564,12 @@ "method": "PATCH", "header": [], "url": { - "raw": "{{APPLICATION_URL}}/account/activate/{{RANDOM_HASH}}", + "raw": "{{APPLICATION_URL}}/user/account/activate/{{RANDOM_HASH}}", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "activate", "{{RANDOM_HASH}}" @@ -571,11 +597,12 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/activate", + "raw": "{{APPLICATION_URL}}/user/account/activate", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "activate" ] @@ -621,11 +648,12 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/reset-password/{{RANDOM_HASH}}", + "raw": "{{APPLICATION_URL}}/user/account/reset-password/{{RANDOM_HASH}}", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "reset-password", "{{RANDOM_HASH}}" @@ -653,11 +681,12 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/recover", + "raw": "{{APPLICATION_URL}}/user/account/recover", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "recover" ] @@ -675,7 +704,8 @@ "exec": [ "" ], - "type": "text/javascript" + "type": "text/javascript", + "packages": {} } } ], @@ -695,13 +725,13 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/register", + "raw": "{{APPLICATION_URL}}/user/account", "host": [ "{{APPLICATION_URL}}" ], "path": [ - "account", - "register" + "user", + "account" ] }, "description": "Guest registers new user account." @@ -726,11 +756,12 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/reset-password", + "raw": "{{APPLICATION_URL}}/user/account/reset-password", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "reset-password" ] @@ -757,11 +788,12 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/account/reset-password", + "raw": "{{APPLICATION_URL}}/user/account/reset-password", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "reset-password" ] @@ -779,11 +811,12 @@ "method": "GET", "header": [], "url": { - "raw": "{{APPLICATION_URL}}/account/reset-password/{{RANDOM_HASH}}", + "raw": "{{APPLICATION_URL}}/user/account/reset-password/{{RANDOM_HASH}}", "host": [ "{{APPLICATION_URL}}" ], "path": [ + "user", "account", "reset-password", "{{RANDOM_HASH}}" @@ -841,7 +874,7 @@ "response": [] } ], - "description": "Authenticated users manage their accounts." + "description": "Users manage their own account." }, { "name": "Role", @@ -872,7 +905,7 @@ "disabled": true }, { - "key": "order", + "key": "sort", "value": "role.created", "disabled": true }, @@ -880,6 +913,11 @@ "key": "dir", "value": "desc", "disabled": true + }, + { + "key": "filters[name]", + "value": "", + "disabled": true } ] }, @@ -907,7 +945,8 @@ }, "response": [] } - ] + ], + "description": "Admins manage user roles." }, { "name": "Security", @@ -921,7 +960,8 @@ "exec": [ "" ], - "type": "text/javascript" + "type": "text/javascript", + "packages": {} } } ], @@ -938,13 +978,13 @@ } }, "url": { - "raw": "{{APPLICATION_URL}}/security/generate-token", + "raw": "{{APPLICATION_URL}}/security/token", "host": [ "{{APPLICATION_URL}}" ], "path": [ "security", - "generate-token" + "token" ] }, "description": "Generate OAuth2 Bearer token for identity with regular user privileges." @@ -992,7 +1032,7 @@ "response": [] } ], - "description": "Generate/Refresh OAuth2 Bearer token with regular user privileges.", + "description": "Users re/generate their access token.", "auth": { "type": "noauth" }, @@ -1101,7 +1141,8 @@ }, "response": [] } - ] + ], + "description": "Admins manage user avatars." }, { "name": "Activate user", @@ -1199,7 +1240,7 @@ "response": [] }, { - "name": "List user", + "name": "List users", "request": { "method": "GET", "header": [], @@ -1225,7 +1266,7 @@ "disabled": true }, { - "key": "order", + "key": "sort", "value": "user.created", "description": "order results by this field", "disabled": true @@ -1235,6 +1276,26 @@ "value": "desc", "description": "order results direction", "disabled": true + }, + { + "key": "filters[identity]", + "value": "", + "disabled": true + }, + { + "key": "filters[email]", + "value": "", + "disabled": true + }, + { + "key": "filters[status]", + "value": "", + "disabled": true + }, + { + "key": "filters[role]", + "value": "", + "disabled": true } ] }, @@ -1289,9 +1350,11 @@ }, "response": [] } - ] + ], + "description": "Admins manage user accounts." } ], + "description": "User module documentation.", "event": [ { "listen": "prerequest", @@ -1408,4 +1471,4 @@ "value": "597155ec70defb9f969c9beaf609814933db53cbcb8b9be6db5e0bf7e051e1e4" } ] -} \ No newline at end of file +} diff --git a/documentation/Dotkernel_API.postman_environment.json b/documentation/Dotkernel_API.postman_environment.json index ff51409e..d1bc2362 100644 --- a/documentation/Dotkernel_API.postman_environment.json +++ b/documentation/Dotkernel_API.postman_environment.json @@ -1,14 +1,26 @@ { - "id": "3ee44887-32cc-4427-891a-4336eff67d80", + "id": "c4763296-be10-4ff4-82c2-583c932c76c4", "name": "Dotkernel_API", "values": [ { "key": "APPLICATION_URL", - "value": "http://localhost:8080", + "value": "http://api.dotkernel.localhost", + "enabled": true + }, + { + "key": "ACCESS_TOKEN", + "value": "", + "type": "any", + "enabled": true + }, + { + "key": "REFRESH_TOKEN", + "value": "", + "type": "any", "enabled": true } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2021-05-14T06:40:40.313Z", - "_postman_exported_using": "Postman/8.4.0" -} \ No newline at end of file + "_postman_exported_at": "2025-04-10T11:59:16.370Z", + "_postman_exported_using": "Postman/11.40.1" +} diff --git a/phpcs.xml b/phpcs.xml index c4307090..f2575524 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -18,11 +18,10 @@ src test - src/Core/src/App/src/Migration/* - + src/Core/src/App/src/Migration/* diff --git a/phpstan.neon b/phpstan.neon index 653f71ea..c9eb57c6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,10 +10,3 @@ parameters: - src - test treatPhpDocTypesAsCertain: false - ignoreErrors: - - - message: '#Call to an undefined method.*setAllowOverride#' - path: test/Functional/AbstractFunctionalTest.php - - - message: '#Call to an undefined method.*setService#' - path: test/Functional/AbstractFunctionalTest.php diff --git a/src/Admin/src/Command/AdminCreateCommand.php b/src/Admin/src/Command/AdminCreateCommand.php index 0078c127..fc21a0f7 100644 --- a/src/Admin/src/Command/AdminCreateCommand.php +++ b/src/Admin/src/Command/AdminCreateCommand.php @@ -5,9 +5,10 @@ namespace Api\Admin\Command; use Api\Admin\InputFilter\CreateAdminInputFilter; +use Api\Admin\Service\AdminRoleServiceInterface; +use Api\Admin\Service\AdminServiceInterface; use Core\Admin\Entity\AdminRole; -use Core\Admin\Service\AdminRoleServiceInterface; -use Core\Admin\Service\AdminServiceInterface; +use Core\Admin\Enum\AdminRoleEnum; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; @@ -78,7 +79,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int throw new BadRequestException(implode(PHP_EOL, $messages)); } - $this->adminService->createAdmin($inputFilter->getValues()); + $this->adminService->saveAdmin($inputFilter->getValues()); (new SymfonyStyle($input, $output))->info(Message::ADMIN_CREATED); @@ -90,7 +91,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int */ private function getData(InputInterface $input): array { - $adminRole = $this->adminRoleService->getAdminRoleRepository()->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + $adminRole = $this->adminRoleService->getAdminRoleRepository()->findOneBy(['name' => AdminRoleEnum::Admin]); if (! $adminRole instanceof AdminRole) { throw new NotFoundException(Message::ROLE_NOT_FOUND); } diff --git a/src/Admin/src/ConfigProvider.php b/src/Admin/src/ConfigProvider.php index a79ffe9e..ccef15bc 100644 --- a/src/Admin/src/ConfigProvider.php +++ b/src/Admin/src/ConfigProvider.php @@ -16,6 +16,10 @@ use Api\Admin\Handler\Admin\PostAdminResourceHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleCollectionHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleResourceHandler; +use Api\Admin\Service\AdminRoleService; +use Api\Admin\Service\AdminRoleServiceInterface; +use Api\Admin\Service\AdminService; +use Api\Admin\Service\AdminServiceInterface; use Api\App\ConfigProvider as AppConfigProvider; use Api\App\Factory\HandlerDelegatorFactory; use Core\Admin\Entity\Admin; @@ -60,6 +64,12 @@ private function getDependencies(): array PatchAdminAccountResourceHandler::class => AttributedServiceFactory::class, PatchAdminResourceHandler::class => AttributedServiceFactory::class, PostAdminResourceHandler::class => AttributedServiceFactory::class, + AdminService::class => AttributedServiceFactory::class, + AdminRoleService::class => AttributedServiceFactory::class, + ], + 'aliases' => [ + AdminServiceInterface::class => AdminService::class, + AdminRoleServiceInterface::class => AdminRoleService::class, ], ]; } diff --git a/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php b/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php index 6bc900f7..1bbfd039 100644 --- a/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php +++ b/src/Admin/src/Handler/Account/PatchAdminAccountResourceHandler.php @@ -5,9 +5,9 @@ namespace Api\Admin\Handler\Account; use Api\Admin\InputFilter\UpdateAdminInputFilter; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; use Core\Admin\Entity\Admin; -use Core\Admin\Service\AdminServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; @@ -39,9 +39,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $admin = $request->getAttribute(Admin::class); - $this->adminService->updateAdmin($admin, (array) $this->inputFilter->getValues()); - - return $this->createResponse($request, $admin); + return $this->createResponse( + $request, + $this->adminService->saveAdmin( + (array) $this->inputFilter->getValues(), + $request->getAttribute(Admin::class) + ) + ); } } diff --git a/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php b/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php index 2f55de23..c5e4d5a9 100644 --- a/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/DeleteAdminResourceHandler.php @@ -4,8 +4,8 @@ namespace Api\Admin\Handler\Admin; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminServiceInterface; use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; @@ -26,9 +26,7 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $this->adminService->getAdminRepository()->deleteAdmin( - $this->adminService->find($request->getAttribute('uuid')) - ); + $this->adminService->deleteAdmin($this->adminService->findAdmin($request->getAttribute('uuid'))); return $this->noContentResponse(); } diff --git a/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php b/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php index 000f3d7c..ee28bde3 100644 --- a/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php +++ b/src/Admin/src/Handler/Admin/GetAdminCollectionHandler.php @@ -5,9 +5,8 @@ namespace Api\Admin\Handler\Admin; use Api\Admin\Collection\AdminCollection; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminServiceInterface; -use Core\App\Exception\BadRequestException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -22,14 +21,11 @@ public function __construct( ) { } - /** - * @throws BadRequestException - */ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - new AdminCollection($this->adminService->getAdminRepository()->getAdmins($request->getQueryParams())) + new AdminCollection($this->adminService->getAdmins($request->getQueryParams())) ); } } diff --git a/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php b/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php index bbbbf2f8..ae68718b 100644 --- a/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/GetAdminResourceHandler.php @@ -4,8 +4,8 @@ namespace Api\Admin\Handler\Admin; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminServiceInterface; use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; @@ -28,7 +28,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - $this->adminService->find($request->getAttribute('uuid')) + $this->adminService->findAdmin($request->getAttribute('uuid')) ); } } diff --git a/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php b/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php index 0cc1e863..906eba6b 100644 --- a/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/PatchAdminResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\Admin\Handler\Admin; use Api\Admin\InputFilter\UpdateAdminInputFilter; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; @@ -38,10 +38,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $admin = $this->adminService->find($request->getAttribute('uuid')); - - $this->adminService->updateAdmin($admin, (array) $this->inputFilter->getValues()); - - return $this->createResponse($request, $admin); + return $this->createResponse( + $request, + $this->adminService->saveAdmin( + (array) $this->inputFilter->getValues(), + $this->adminService->findAdmin($request->getAttribute('uuid')) + ) + ); } } diff --git a/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php b/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php index 19059d24..c40c47f3 100644 --- a/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php +++ b/src/Admin/src/Handler/Admin/PostAdminResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\Admin\Handler\Admin; use Api\Admin\InputFilter\CreateAdminInputFilter; +use Api\Admin\Service\AdminServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; @@ -40,7 +40,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface return $this->createdResponse( $request, - $this->adminService->createAdmin((array) $this->inputFilter->getValues()) + $this->adminService->saveAdmin((array) $this->inputFilter->getValues()) ); } } diff --git a/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php b/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php index 61730701..c94ceb3d 100644 --- a/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php +++ b/src/Admin/src/Handler/Admin/Role/GetAdminRoleCollectionHandler.php @@ -5,9 +5,8 @@ namespace Api\Admin\Handler\Admin\Role; use Api\Admin\Collection\AdminRoleCollection; +use Api\Admin\Service\AdminRoleServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminRoleServiceInterface; -use Core\App\Exception\BadRequestException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -22,15 +21,12 @@ public function __construct( ) { } - /** - * @throws BadRequestException - */ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, new AdminRoleCollection( - $this->adminRoleService->getAdminRoleRepository()->getAdminRoles($request->getQueryParams()) + $this->adminRoleService->getAdminRoles($request->getQueryParams()) ) ); } diff --git a/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php b/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php index 479247c8..c3260670 100644 --- a/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php +++ b/src/Admin/src/Handler/Admin/Role/GetAdminRoleResourceHandler.php @@ -4,8 +4,8 @@ namespace Api\Admin\Handler\Admin\Role; +use Api\Admin\Service\AdminRoleServiceInterface; use Api\App\Handler\AbstractHandler; -use Core\Admin\Service\AdminRoleServiceInterface; use Core\App\Exception\NotFoundException; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; @@ -28,7 +28,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - $this->adminRoleService->find($request->getAttribute('uuid')) + $this->adminRoleService->findAdminRole($request->getAttribute('uuid')) ); } } diff --git a/src/Admin/src/InputFilter/AdminRoleInputFilter.php b/src/Admin/src/InputFilter/AdminRoleInputFilter.php index dfa10e4a..021b9621 100644 --- a/src/Admin/src/InputFilter/AdminRoleInputFilter.php +++ b/src/Admin/src/InputFilter/AdminRoleInputFilter.php @@ -4,13 +4,10 @@ namespace Api\Admin\InputFilter; -use Api\Admin\InputFilter\Input\UuidInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\UuidInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class AdminRoleInputFilter extends InputFilter +class AdminRoleInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/Admin/src/InputFilter/CreateAdminInputFilter.php b/src/Admin/src/InputFilter/CreateAdminInputFilter.php index bf69ff1a..149357ff 100644 --- a/src/Admin/src/InputFilter/CreateAdminInputFilter.php +++ b/src/Admin/src/InputFilter/CreateAdminInputFilter.php @@ -4,19 +4,16 @@ namespace Api\Admin\InputFilter; -use Api\Admin\InputFilter\Input\FirstNameInput; -use Api\Admin\InputFilter\Input\IdentityInput; -use Api\Admin\InputFilter\Input\LastNameInput; -use Api\Admin\InputFilter\Input\PasswordConfirmInput; -use Api\Admin\InputFilter\Input\PasswordInput; use Api\Admin\InputFilter\Input\StatusInput; +use Api\App\InputFilter\Input\FirstNameInput; +use Api\App\InputFilter\Input\IdentityInput; +use Api\App\InputFilter\Input\LastNameInput; +use Api\App\InputFilter\Input\PasswordConfirmInput; +use Api\App\InputFilter\Input\PasswordInput; +use Core\App\InputFilter\AbstractInputFilter; use Laminas\InputFilter\CollectionInputFilter; -use Laminas\InputFilter\InputFilter; -/** - * @extends InputFilter - */ -class CreateAdminInputFilter extends InputFilter +class CreateAdminInputFilter extends AbstractInputFilter { public function __construct() { @@ -28,9 +25,9 @@ public function __construct() ->add(new IdentityInput('identity')) ->add(new PasswordInput('password')) ->add(new PasswordConfirmInput('passwordConfirm')) - ->add(new FirstNameInput('firstName')) - ->add(new LastNameInput('lastName')) - ->add(new StatusInput('status', false)) + ->add(new FirstNameInput('firstName', false)) + ->add(new LastNameInput('lastName', false)) + ->add(new StatusInput('status')) ->add($roles, 'roles'); } } diff --git a/src/Admin/src/InputFilter/Input/FirstNameInput.php b/src/Admin/src/InputFilter/Input/FirstNameInput.php deleted file mode 100644 index a1261f6b..00000000 --- a/src/Admin/src/InputFilter/Input/FirstNameInput.php +++ /dev/null @@ -1,32 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - - $this->getValidatorChain() - ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'First name'), - ], true); - } -} diff --git a/src/Admin/src/InputFilter/Input/IdentityInput.php b/src/Admin/src/InputFilter/Input/IdentityInput.php deleted file mode 100644 index 6d1fb50f..00000000 --- a/src/Admin/src/InputFilter/Input/IdentityInput.php +++ /dev/null @@ -1,32 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - - $this->getValidatorChain() - ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Identity'), - ], true); - } -} diff --git a/src/Admin/src/InputFilter/Input/PasswordConfirmInput.php b/src/Admin/src/InputFilter/Input/PasswordConfirmInput.php deleted file mode 100644 index 386c5c61..00000000 --- a/src/Admin/src/InputFilter/Input/PasswordConfirmInput.php +++ /dev/null @@ -1,31 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - - $this->getValidatorChain() - ->attachByName(Identical::class, [ - 'token' => 'password', - 'message' => Message::VALIDATOR_PASSWORD_MISMATCH, - ], true); - } -} diff --git a/src/Admin/src/InputFilter/Input/StatusInput.php b/src/Admin/src/InputFilter/Input/StatusInput.php index 5dcfda15..28565080 100644 --- a/src/Admin/src/InputFilter/Input/StatusInput.php +++ b/src/Admin/src/InputFilter/Input/StatusInput.php @@ -11,8 +11,6 @@ use Laminas\InputFilter\Input; use Laminas\Validator\InArray; -use function sprintf; - class StatusInput extends Input { public function __construct(?string $name = null, bool $isRequired = true) @@ -29,7 +27,7 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(InArray::class, [ 'haystack' => AdminStatusEnum::cases(), - 'message' => sprintf(Message::INVALID_VALUE, 'status'), + 'message' => Message::invalidValue('status'), ], true); } } diff --git a/src/Admin/src/InputFilter/Input/UuidInput.php b/src/Admin/src/InputFilter/Input/UuidInput.php deleted file mode 100644 index 71a5a9da..00000000 --- a/src/Admin/src/InputFilter/Input/UuidInput.php +++ /dev/null @@ -1,23 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - } -} diff --git a/src/Admin/src/InputFilter/UpdateAdminInputFilter.php b/src/Admin/src/InputFilter/UpdateAdminInputFilter.php index 3cb46297..c932029b 100644 --- a/src/Admin/src/InputFilter/UpdateAdminInputFilter.php +++ b/src/Admin/src/InputFilter/UpdateAdminInputFilter.php @@ -4,18 +4,15 @@ namespace Api\Admin\InputFilter; -use Api\Admin\InputFilter\Input\FirstNameInput; -use Api\Admin\InputFilter\Input\LastNameInput; -use Api\Admin\InputFilter\Input\PasswordConfirmInput; -use Api\Admin\InputFilter\Input\PasswordInput; use Api\Admin\InputFilter\Input\StatusInput; +use Api\App\InputFilter\Input\FirstNameInput; +use Api\App\InputFilter\Input\LastNameInput; +use Api\App\InputFilter\Input\PasswordConfirmInput; +use Api\App\InputFilter\Input\PasswordInput; +use Core\App\InputFilter\AbstractInputFilter; use Laminas\InputFilter\CollectionInputFilter; -use Laminas\InputFilter\InputFilter; -/** - * @extends InputFilter - */ -class UpdateAdminInputFilter extends InputFilter +class UpdateAdminInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/Admin/src/OpenAPI.php b/src/Admin/src/OpenAPI.php index 96048f35..4207cea0 100644 --- a/src/Admin/src/OpenAPI.php +++ b/src/Admin/src/OpenAPI.php @@ -17,6 +17,7 @@ use Api\Admin\Handler\Admin\Role\GetAdminRoleResourceHandler; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Core\Admin\Enum\AdminStatusEnum; use DateTimeImmutable; use Fig\Http\Message\StatusCodeInterface; @@ -454,7 +455,7 @@ items: new OA\Items( properties: [ new OA\Property(property: 'uuid', type: 'string'), - new OA\Property(property: 'name', type: 'string', example: AdminRole::ROLE_ADMIN), + new OA\Property(property: 'name', type: 'string', example: AdminRoleEnum::Admin->value), ], type: 'object', ), @@ -489,7 +490,7 @@ schema: 'AdminRole', properties: [ new OA\Property(property: 'uuid', type: 'string', example: '1234abcd-abcd-4321-12ab-123456abcdef'), - new OA\Property(property: 'name', type: 'string', example: AdminRole::ROLE_ADMIN), + new OA\Property(property: 'name', type: 'string', example: AdminRoleEnum::Admin->value), new OA\Property( property: '_links', properties: [ diff --git a/src/Admin/src/RoutesDelegator.php b/src/Admin/src/RoutesDelegator.php index 8e8103fd..35a6e75a 100644 --- a/src/Admin/src/RoutesDelegator.php +++ b/src/Admin/src/RoutesDelegator.php @@ -13,6 +13,7 @@ use Api\Admin\Handler\Admin\PostAdminResourceHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleCollectionHandler; use Api\Admin\Handler\Admin\Role\GetAdminRoleResourceHandler; +use Core\App\ConfigProvider; use Dot\Router\RouteCollectorInterface; use Mezzio\Application; use Psr\Container\ContainerExceptionInterface; @@ -27,7 +28,7 @@ class RoutesDelegator */ public function __invoke(ContainerInterface $container, string $serviceName, callable $callback): Application { - $uuid = \Api\App\RoutesDelegator::REGEXP_UUID; + $uuid = ConfigProvider::REGEXP_UUID; /** @var RouteCollectorInterface $routeCollector */ $routeCollector = $container->get(RouteCollectorInterface::class); diff --git a/src/Core/src/Admin/src/Service/AdminRoleService.php b/src/Admin/src/Service/AdminRoleService.php similarity index 53% rename from src/Core/src/Admin/src/Service/AdminRoleService.php rename to src/Admin/src/Service/AdminRoleService.php index c352ac08..e57cda67 100644 --- a/src/Core/src/Admin/src/Service/AdminRoleService.php +++ b/src/Admin/src/Service/AdminRoleService.php @@ -2,14 +2,18 @@ declare(strict_types=1); -namespace Core\Admin\Service; +namespace Api\Admin\Service; use Core\Admin\Entity\AdminRole; use Core\Admin\Repository\AdminRoleRepository; use Core\App\Exception\NotFoundException; +use Core\App\Helper\Paginator; use Core\App\Message; +use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Inject; +use function in_array; + class AdminRoleService implements AdminRoleServiceInterface { #[Inject( @@ -28,7 +32,7 @@ public function getAdminRoleRepository(): AdminRoleRepository /** * @throws NotFoundException */ - public function find(string $id): AdminRole + public function findAdminRole(string $id): AdminRole { $adminRole = $this->adminRoleRepository->find($id); if (! $adminRole instanceof AdminRole) { @@ -37,4 +41,24 @@ public function find(string $id): AdminRole return $adminRole; } + + /** + * @param array $params + */ + public function getAdminRoles(array $params): QueryBuilder + { + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($params, 'role.created'); + + $sortableColumns = [ + 'role.name', + 'role.created', + 'role.updated', + ]; + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'role.created'; + } + + return $this->adminRoleRepository->getAdminRoles($params, $filters); + } } diff --git a/src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php b/src/Admin/src/Service/AdminRoleServiceInterface.php similarity index 56% rename from src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php rename to src/Admin/src/Service/AdminRoleServiceInterface.php index bc7695c5..bf6003c8 100644 --- a/src/Core/src/Admin/src/Service/AdminRoleServiceInterface.php +++ b/src/Admin/src/Service/AdminRoleServiceInterface.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Core\Admin\Service; +namespace Api\Admin\Service; use Core\Admin\Entity\AdminRole; use Core\Admin\Repository\AdminRoleRepository; use Core\App\Exception\NotFoundException; +use Doctrine\ORM\QueryBuilder; interface AdminRoleServiceInterface { @@ -15,5 +16,10 @@ public function getAdminRoleRepository(): AdminRoleRepository; /** * @throws NotFoundException */ - public function find(string $id): AdminRole; + public function findAdminRole(string $id): AdminRole; + + /** + * @param array $params + */ + public function getAdminRoles(array $params): QueryBuilder; } diff --git a/src/Core/src/Admin/src/Service/AdminService.php b/src/Admin/src/Service/AdminService.php similarity index 52% rename from src/Core/src/Admin/src/Service/AdminService.php rename to src/Admin/src/Service/AdminService.php index 7463c5c6..ebe557f7 100644 --- a/src/Core/src/Admin/src/Service/AdminService.php +++ b/src/Admin/src/Service/AdminService.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Core\Admin\Service; +namespace Api\Admin\Service; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; @@ -12,10 +12,16 @@ use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; +use Core\App\Helper\Paginator; use Core\App\Message; +use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Inject; use Ramsey\Uuid\UuidInterface; +use function array_key_exists; +use function count; +use function in_array; + class AdminService implements AdminServiceInterface { #[Inject( @@ -33,43 +39,17 @@ public function getAdminRepository(): AdminRepository return $this->adminRepository; } - /** - * @throws ConflictException - * @throws NotFoundException - */ - public function createAdmin(array $data = []): Admin - { - $admin = (new Admin()) - ->setIdentity($data['identity']) - ->usePassword($data['password']) - ->setFirstName($data['firstName']) - ->setLastName($data['lastName']) - ->setStatus($data['status'] ?? AdminStatusEnum::Active); - - $this->validateUniqueUser($admin->getIdentity()); - - foreach ($data['roles'] as $roleData) { - $adminRole = $this->adminRoleRepository->find($roleData['uuid']); - if (! $adminRole instanceof AdminRole) { - throw new NotFoundException(Message::ROLE_NOT_FOUND); - } - $admin->addRole($adminRole); - } - - return $this->adminRepository->saveAdmin($admin); - } - public function deleteAdmin(Admin $admin): void { - $this->adminRepository->deleteAdmin($admin); + $this->adminRepository->deleteResource($admin); } /** * @throws NotFoundException */ - public function find(string $id): Admin + public function findAdmin(string $uuid): Admin { - $admin = $this->adminRepository->find($id); + $admin = $this->adminRepository->find($uuid); if (! $admin instanceof Admin) { throw new NotFoundException(Message::ADMIN_NOT_FOUND); } @@ -77,32 +57,67 @@ public function find(string $id): Admin return $admin; } + /** + * @param array $params + */ + public function getAdmins(array $params): QueryBuilder + { + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($params, 'admin.created'); + + $sortableColumns = [ + 'admin.identity', + 'admin.firstName', + 'admin.lastName', + 'admin.status', + 'admin.created', + 'admin.updated', + 'role.name', + ]; + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'admin.created'; + } + + return $this->adminRepository->getAdmins($params, $filters); + } + /** * @throws BadRequestException * @throws ConflictException * @throws NotFoundException */ - public function updateAdmin(Admin $admin, array $data = []): Admin + public function saveAdmin(array $data, ?Admin $admin = null): Admin { - if (! empty($data['password'])) { - $admin->usePassword($data['password']); + if (! $admin instanceof Admin) { + $admin = new Admin(); } - if (isset($data['firstName'])) { + if (array_key_exists('identity', $data) && $data['identity'] !== null && ! $admin->hasIdentity()) { + $admin->setIdentity($data['identity']); + } + if (array_key_exists('password', $data) && $data['password'] !== null) { + $admin->usePassword($data['password']); + } + if (array_key_exists('firstName', $data) && $data['firstName'] !== null) { $admin->setFirstName($data['firstName']); } - - if (isset($data['lastName'])) { + if (array_key_exists('lastName', $data) && $data['lastName'] !== null) { $admin->setLastName($data['lastName']); } - - if (isset($data['status'])) { - $admin->setStatus($data['status']); + if (array_key_exists('status', $data) && $data['status'] !== null) { + $status = $data['status']; + if (! $status instanceof AdminStatusEnum) { + $status = AdminStatusEnum::tryFrom($status); + } + if (! $status instanceof AdminStatusEnum) { + throw new BadRequestException(Message::invalidValue('status')); + } + $admin->setStatus($status); } - $this->validateUniqueUser($admin->getIdentity(), $admin->getUuid()); + $this->validateUniqueAdmin($admin->getIdentity(), $admin->getUuid()); - if (! empty($data['roles'])) { + if (array_key_exists('roles', $data) && count($data['roles']) > 0) { $admin->resetRoles(); foreach ($data['roles'] as $roleData) { $adminRole = $this->adminRoleRepository->find($roleData['uuid']); @@ -117,13 +132,15 @@ public function updateAdmin(Admin $admin, array $data = []): Admin throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); } - return $this->adminRepository->saveAdmin($admin); + $this->adminRepository->saveResource($admin); + + return $admin; } /** * @throws ConflictException */ - public function validateUniqueUser(string $identity, ?UuidInterface $uuid = null): void + public function validateUniqueAdmin(string $identity, ?UuidInterface $uuid = null): void { $admin = $this->adminRepository->findOneBy(['identity' => $identity]); if ($admin instanceof Admin) { diff --git a/src/Core/src/Admin/src/Service/AdminServiceInterface.php b/src/Admin/src/Service/AdminServiceInterface.php similarity index 66% rename from src/Core/src/Admin/src/Service/AdminServiceInterface.php rename to src/Admin/src/Service/AdminServiceInterface.php index 1f1139c2..c8bf2b93 100644 --- a/src/Core/src/Admin/src/Service/AdminServiceInterface.php +++ b/src/Admin/src/Service/AdminServiceInterface.php @@ -2,35 +2,35 @@ declare(strict_types=1); -namespace Core\Admin\Service; +namespace Api\Admin\Service; use Core\Admin\Entity\Admin; use Core\Admin\Repository\AdminRepository; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; +use Doctrine\ORM\QueryBuilder; interface AdminServiceInterface { public function getAdminRepository(): AdminRepository; + public function deleteAdmin(Admin $admin): void; + /** - * @throws ConflictException * @throws NotFoundException */ - public function createAdmin(array $data = []): Admin; - - public function deleteAdmin(Admin $admin): void; + public function findAdmin(string $uuid): Admin; /** - * @throws NotFoundException + * @param array $params */ - public function find(string $id): Admin; + public function getAdmins(array $params): QueryBuilder; /** * @throws BadRequestException * @throws ConflictException * @throws NotFoundException */ - public function updateAdmin(Admin $admin, array $data = []): Admin; + public function saveAdmin(array $data, ?Admin $admin = null): Admin; } diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index bcafb02b..8ac66e93 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -4,7 +4,6 @@ namespace Api\App; -use Api\App\Command\RouteListCommand; use Api\App\Command\TokenGenerateCommand; use Api\App\Factory\HandlerDelegatorFactory; use Api\App\Handler\GetIndexResourceHandler; @@ -57,7 +56,6 @@ private function getDependencies(): array GetIndexResourceHandler::class => AttributedServiceFactory::class, PostErrorReportResourceHandler::class => AttributedServiceFactory::class, ErrorReportService::class => AttributedServiceFactory::class, - RouteListCommand::class => AttributedServiceFactory::class, TokenGenerateCommand::class => AttributedServiceFactory::class, Environment::class => TwigEnvironmentFactory::class, TwigExtension::class => TwigExtensionFactory::class, diff --git a/src/App/src/Handler/GetIndexResourceHandler.php b/src/App/src/Handler/GetIndexResourceHandler.php index 3e0e9064..0f50451a 100644 --- a/src/App/src/Handler/GetIndexResourceHandler.php +++ b/src/App/src/Handler/GetIndexResourceHandler.php @@ -19,15 +19,17 @@ class GetIndexResourceHandler extends AbstractHandler { #[Inject( - 'config.application.name', + 'config.application', )] public function __construct( - private readonly string $applicationName, + private readonly array $config, ) { } public function handle(ServerRequestInterface $request): ResponseInterface { - return $this->jsonResponse(['message' => sprintf('%s version 5', $this->applicationName)]); + return $this->jsonResponse([ + 'message' => sprintf('%s version %s', $this->config['name'], $this->config['version']), + ]); } } diff --git a/src/App/src/InputFilter/ErrorReportInputFilter.php b/src/App/src/InputFilter/ErrorReportInputFilter.php index eb7f465c..c0e113ea 100644 --- a/src/App/src/InputFilter/ErrorReportInputFilter.php +++ b/src/App/src/InputFilter/ErrorReportInputFilter.php @@ -5,12 +5,9 @@ namespace Api\App\InputFilter; use Api\App\InputFilter\Input\MessageInput; -use Laminas\InputFilter\InputFilter; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class ErrorReportInputFilter extends InputFilter +class ErrorReportInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/App/src/InputFilter/Input/EmailInput.php b/src/App/src/InputFilter/Input/EmailInput.php new file mode 100644 index 00000000..b62c62d7 --- /dev/null +++ b/src/App/src/InputFilter/Input/EmailInput.php @@ -0,0 +1,41 @@ +setRequired($isRequired); + + $this->getFilterChain() + ->attachByName(StringTrim::class) + ->attachByName(StripTags::class); + + $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true) + ->attachByName(EmailAddress::class, [ + 'message' => Message::VALIDATOR_INVALID_EMAIL, + ], true) + ->attachByName(StringLength::class, [ + 'max' => self::EMAIL_MAX_LENGTH, + 'message' => Message::validatorLengthMax(self::EMAIL_MAX_LENGTH), + ], true); + } +} diff --git a/src/Admin/src/InputFilter/Input/PasswordInput.php b/src/App/src/InputFilter/Input/FirstNameInput.php similarity index 63% rename from src/Admin/src/InputFilter/Input/PasswordInput.php rename to src/App/src/InputFilter/Input/FirstNameInput.php index 22d3cf31..29323a8b 100644 --- a/src/Admin/src/InputFilter/Input/PasswordInput.php +++ b/src/App/src/InputFilter/Input/FirstNameInput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\Admin\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; @@ -11,11 +11,9 @@ use Laminas\Validator\NotEmpty; use Laminas\Validator\StringLength; -use function sprintf; - -class PasswordInput extends Input +class FirstNameInput extends Input { - public const PASSWORD_MIN_LENGTH = 6; + public const FIRSTNAME_MAX_LENGTH = 191; public function __construct(?string $name = null, bool $isRequired = true) { @@ -28,12 +26,12 @@ public function __construct(?string $name = null, bool $isRequired = true) ->attachByName(StripTags::class); $this->getValidatorChain() - ->attachByName(StringLength::class, [ - 'min' => self::PASSWORD_MIN_LENGTH, - 'message' => sprintf(Message::VALIDATOR_MIN_LENGTH, 'Password', self::PASSWORD_MIN_LENGTH), - ], true) ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Password'), + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true) + ->attachByName(StringLength::class, [ + 'max' => self::FIRSTNAME_MAX_LENGTH, + 'message' => Message::validatorLengthMax(self::FIRSTNAME_MAX_LENGTH), ], true); } } diff --git a/src/User/src/InputFilter/Input/IdentityInput.php b/src/App/src/InputFilter/Input/IdentityInput.php similarity index 54% rename from src/User/src/InputFilter/Input/IdentityInput.php rename to src/App/src/InputFilter/Input/IdentityInput.php index 326441e4..c4ac0662 100644 --- a/src/User/src/InputFilter/Input/IdentityInput.php +++ b/src/App/src/InputFilter/Input/IdentityInput.php @@ -2,18 +2,20 @@ declare(strict_types=1); -namespace Api\User\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; use Laminas\Filter\StripTags; use Laminas\InputFilter\Input; use Laminas\Validator\NotEmpty; - -use function sprintf; +use Laminas\Validator\StringLength; class IdentityInput extends Input { + public const IDENTITY_MIN_LENGTH = 3; + public const IDENTITY_MAX_LENGTH = 100; + public function __construct(?string $name = null, bool $isRequired = true) { parent::__construct($name); @@ -26,7 +28,12 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Identity'), + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true) + ->attachByName(StringLength::class, [ + 'min' => self::IDENTITY_MIN_LENGTH, + 'max' => self::IDENTITY_MAX_LENGTH, + 'message' => Message::validatorLengthMinMax(self::IDENTITY_MIN_LENGTH, self::IDENTITY_MAX_LENGTH), ], true); } } diff --git a/src/User/src/InputFilter/Input/AvatarInput.php b/src/App/src/InputFilter/Input/ImageInput.php similarity index 62% rename from src/User/src/InputFilter/Input/AvatarInput.php rename to src/App/src/InputFilter/Input/ImageInput.php index 1b1435fd..a0a924a6 100644 --- a/src/User/src/InputFilter/Input/AvatarInput.php +++ b/src/App/src/InputFilter/Input/ImageInput.php @@ -2,18 +2,21 @@ declare(strict_types=1); -namespace Api\User\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; use Laminas\Filter\StripTags; use Laminas\InputFilter\FileInput; use Laminas\Validator\File\IsImage; +use Laminas\Validator\File\MimeType; use Laminas\Validator\File\UploadFile; use Laminas\Validator\NotEmpty; -class AvatarInput extends FileInput +class ImageInput extends FileInput { + public static array $mimeTypes = ['image/jpeg', 'image/png']; + public function __construct(?string $name = null, bool $isRequired = true) { parent::__construct($name); @@ -30,8 +33,13 @@ public function __construct(?string $name = null, bool $isRequired = true) ], true) ->attachByName(UploadFile::class, [ 'message' => Message::VALIDATOR_REQUIRED_UPLOAD, - ], true)->attachByName(IsImage::class, [ - 'message' => Message::RESTRICTION_IMAGE, + ], true) + ->attachByName(IsImage::class, [ + 'message' => Message::restrictionImage(self::$mimeTypes), + ], true) + ->attachByName(MimeType::class, [ + 'mimeType' => self::$mimeTypes, + 'message' => Message::restrictionImage(self::$mimeTypes), ], true); } } diff --git a/src/Admin/src/InputFilter/Input/LastNameInput.php b/src/App/src/InputFilter/Input/LastNameInput.php similarity index 61% rename from src/Admin/src/InputFilter/Input/LastNameInput.php rename to src/App/src/InputFilter/Input/LastNameInput.php index 77851797..c94b45e9 100644 --- a/src/Admin/src/InputFilter/Input/LastNameInput.php +++ b/src/App/src/InputFilter/Input/LastNameInput.php @@ -2,18 +2,19 @@ declare(strict_types=1); -namespace Api\Admin\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; use Laminas\Filter\StripTags; use Laminas\InputFilter\Input; use Laminas\Validator\NotEmpty; - -use function sprintf; +use Laminas\Validator\StringLength; class LastNameInput extends Input { + public const LASTNAME_MAX_LENGTH = 191; + public function __construct(?string $name = null, bool $isRequired = true) { parent::__construct($name); @@ -26,7 +27,11 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Last name'), + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true) + ->attachByName(StringLength::class, [ + 'max' => self::LASTNAME_MAX_LENGTH, + 'message' => Message::validatorLengthMax(self::LASTNAME_MAX_LENGTH), ], true); } } diff --git a/src/App/src/InputFilter/Input/MessageInput.php b/src/App/src/InputFilter/Input/MessageInput.php index d5f1e2b4..81eccc66 100644 --- a/src/App/src/InputFilter/Input/MessageInput.php +++ b/src/App/src/InputFilter/Input/MessageInput.php @@ -10,8 +10,6 @@ use Laminas\InputFilter\Input; use Laminas\Validator\NotEmpty; -use function sprintf; - class MessageInput extends Input { public function __construct(?string $name = null, bool $isRequired = true) @@ -26,7 +24,7 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Message'), + 'message' => Message::VALIDATOR_REQUIRED_FIELD, ], true); } } diff --git a/src/User/src/InputFilter/Input/PasswordConfirmInput.php b/src/App/src/InputFilter/Input/PasswordConfirmInput.php similarity index 52% rename from src/User/src/InputFilter/Input/PasswordConfirmInput.php rename to src/App/src/InputFilter/Input/PasswordConfirmInput.php index 2699d3a4..96c28944 100644 --- a/src/User/src/InputFilter/Input/PasswordConfirmInput.php +++ b/src/App/src/InputFilter/Input/PasswordConfirmInput.php @@ -2,15 +2,12 @@ declare(strict_types=1); -namespace Api\User\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; -use Laminas\Filter\StringTrim; -use Laminas\Filter\StripTags; -use Laminas\InputFilter\Input; use Laminas\Validator\Identical; -class PasswordConfirmInput extends Input +class PasswordConfirmInput extends PasswordInput { public function __construct(?string $name = null, bool $isRequired = true) { @@ -18,14 +15,10 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->setRequired($isRequired); - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - $this->getValidatorChain() ->attachByName(Identical::class, [ 'token' => 'password', - 'message' => Message::VALIDATOR_PASSWORD_MISMATCH, + 'message' => Message::validatorMismatch('Password', 'Confirm password'), ], true); } } diff --git a/src/User/src/InputFilter/Input/PasswordInput.php b/src/App/src/InputFilter/Input/PasswordInput.php similarity index 68% rename from src/User/src/InputFilter/Input/PasswordInput.php rename to src/App/src/InputFilter/Input/PasswordInput.php index 1439849d..0dc1dded 100644 --- a/src/User/src/InputFilter/Input/PasswordInput.php +++ b/src/App/src/InputFilter/Input/PasswordInput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\User\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; @@ -11,11 +11,10 @@ use Laminas\Validator\NotEmpty; use Laminas\Validator\StringLength; -use function sprintf; - class PasswordInput extends Input { - public const PASSWORD_MIN_LENGTH = 6; + public const PASSWORD_MIN_LENGTH = 8; + public const PASSWORD_MAX_LENGTH = 150; public function __construct(?string $name = null, bool $isRequired = true) { @@ -28,12 +27,13 @@ public function __construct(?string $name = null, bool $isRequired = true) ->attachByName(StripTags::class); $this->getValidatorChain() + ->attachByName(NotEmpty::class, [ + 'message' => Message::VALIDATOR_REQUIRED_FIELD, + ], true) ->attachByName(StringLength::class, [ 'min' => self::PASSWORD_MIN_LENGTH, - 'message' => sprintf(Message::VALIDATOR_MIN_LENGTH, 'Password', self::PASSWORD_MIN_LENGTH), - ], true) - ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Password'), + 'max' => self::PASSWORD_MAX_LENGTH, + 'message' => Message::validatorLengthMinMax(self::PASSWORD_MIN_LENGTH, self::PASSWORD_MAX_LENGTH), ], true); } } diff --git a/src/User/src/InputFilter/Input/EmailInput.php b/src/App/src/InputFilter/Input/UuidInput.php similarity index 76% rename from src/User/src/InputFilter/Input/EmailInput.php rename to src/App/src/InputFilter/Input/UuidInput.php index 322311d7..84dd7bbd 100644 --- a/src/User/src/InputFilter/Input/EmailInput.php +++ b/src/App/src/InputFilter/Input/UuidInput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Api\User\InputFilter\Input; +namespace Api\App\InputFilter\Input; use Core\App\Message; use Laminas\Filter\StringTrim; @@ -10,9 +10,7 @@ use Laminas\InputFilter\Input; use Laminas\Validator\NotEmpty; -use function sprintf; - -class EmailInput extends Input +class UuidInput extends Input { public function __construct(?string $name = null, bool $isRequired = true) { @@ -26,7 +24,7 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(NotEmpty::class, [ - 'message' => sprintf(Message::VALIDATOR_REQUIRED_FIELD_BY_NAME, 'Email'), + 'message' => Message::VALIDATOR_REQUIRED_FIELD, ], true); } } diff --git a/src/App/src/Middleware/AuthenticationMiddleware.php b/src/App/src/Middleware/AuthenticationMiddleware.php index 152e0be8..e76fc12c 100644 --- a/src/App/src/Middleware/AuthenticationMiddleware.php +++ b/src/App/src/Middleware/AuthenticationMiddleware.php @@ -4,7 +4,7 @@ namespace Api\App\Middleware; -use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\UserIdentity; use Dot\DependencyInjection\Attribute\Inject; use Mezzio\Authentication\AuthenticationInterface; @@ -29,7 +29,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface $user = $this->auth->authenticate($request); if (! $user instanceof UserIdentity) { $user = new UserIdentity('guest', [ - UserRole::ROLE_GUEST, + UserRoleEnum::Guest, ], [ 'oauth_client_id' => 'guest', ]); diff --git a/src/App/src/Middleware/AuthorizationMiddleware.php b/src/App/src/Middleware/AuthorizationMiddleware.php index 36b1ad62..0a920514 100644 --- a/src/App/src/Middleware/AuthorizationMiddleware.php +++ b/src/App/src/Middleware/AuthorizationMiddleware.php @@ -22,8 +22,8 @@ use Psr\Http\Server\MiddlewareInterface; use Psr\Http\Server\RequestHandlerInterface; +use function array_map; use function assert; -use function sprintf; class AuthorizationMiddleware implements MiddlewareInterface { @@ -48,23 +48,17 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface case 'admin': $user = $this->adminRepository->findOneBy(['identity' => $defaultUser->getIdentity()]); if (! $user instanceof Admin) { - return $this->unauthorizedResponse(sprintf( - Message::USER_NOT_FOUND_BY_IDENTITY, - $defaultUser->getIdentity() - )); + return $this->unauthorizedResponse(Message::ADMIN_NOT_FOUND); } if (! $user->isActive()) { - return $this->unauthorizedResponse(Message::ADMIN_NOT_ACTIVATED); + return $this->unauthorizedResponse(Message::ADMIN_INACTIVE); } $request = $request->withAttribute(Admin::class, $user); break; case 'frontend': $user = $this->userRepository->findOneBy(['identity' => $defaultUser->getIdentity()]); if (! $user instanceof User || $user->isDeleted()) { - return $this->unauthorizedResponse(sprintf( - Message::USER_NOT_FOUND_BY_IDENTITY, - $defaultUser->getIdentity() - )); + return $this->unauthorizedResponse(Message::USER_NOT_FOUND); } if (! $user->isActive()) { return $this->unauthorizedResponse(Message::USER_NOT_ACTIVATED); @@ -79,9 +73,9 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface return $this->unauthorizedResponse(Message::INVALID_CLIENT_ID); } - $defaultUser->setRoles($user->getRoles()->map(function (RoleInterface $role) { - return $role->getName(); - })->toArray()); + $defaultUser->setRoles( + array_map(fn (RoleInterface $role): string => $role->getName()->value, $user->getRoles()) + ); $request = $request->withAttribute(UserInterface::class, $defaultUser); diff --git a/src/App/src/Middleware/ContentNegotiationMiddleware.php b/src/App/src/Middleware/ContentNegotiationMiddleware.php index 3135e62f..88155600 100644 --- a/src/App/src/Middleware/ContentNegotiationMiddleware.php +++ b/src/App/src/Middleware/ContentNegotiationMiddleware.php @@ -67,9 +67,10 @@ public function process( public function formatAcceptRequest(string $accept): array { - $accept = array_map(function ($item) { - return trim((string) strtok($item, ';')); - }, explode(',', $accept)); + $accept = array_map( + fn ($item): string => trim((string) strtok($item, ';')), + explode(',', $accept) + ); return array_filter($accept); } @@ -122,9 +123,7 @@ public function validateResponseContentType(?string $contentType, array $accept) return false; } - $accept = array_map(function ($item) { - return str_contains($item, 'json') ? 'json' : $item; - }, $accept); + $accept = array_map(fn (string $item): string => str_contains($item, 'json') ? 'json' : $item, $accept); if (str_contains($contentType, 'json')) { $contentType = 'json'; diff --git a/src/App/src/RoutesDelegator.php b/src/App/src/RoutesDelegator.php index d08f7af7..41238429 100644 --- a/src/App/src/RoutesDelegator.php +++ b/src/App/src/RoutesDelegator.php @@ -15,8 +15,6 @@ class RoutesDelegator { - public const REGEXP_UUID = '{uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}}'; - /** * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface diff --git a/src/Core/src/Admin/src/ConfigProvider.php b/src/Core/src/Admin/src/ConfigProvider.php index 249d37d8..a9428d92 100644 --- a/src/Core/src/Admin/src/ConfigProvider.php +++ b/src/Core/src/Admin/src/ConfigProvider.php @@ -4,18 +4,13 @@ namespace Core\Admin; +use Core\Admin\DBAL\Types\AdminRoleEnumType; use Core\Admin\DBAL\Types\AdminStatusEnumType; +use Core\Admin\Repository\AdminLoginRepository; use Core\Admin\Repository\AdminRepository; use Core\Admin\Repository\AdminRoleRepository; -use Core\Admin\Service\AdminRoleService; -use Core\Admin\Service\AdminRoleServiceInterface; -use Core\Admin\Service\AdminService; -use Core\Admin\Service\AdminServiceInterface; use Doctrine\ORM\Mapping\Driver\AttributeDriver; use Dot\DependencyInjection\Factory\AttributedRepositoryFactory; -use Dot\DependencyInjection\Factory\AttributedServiceFactory; - -use function getcwd; class ConfigProvider { @@ -31,14 +26,9 @@ private function getDependencies(): array { return [ 'factories' => [ - AdminService::class => AttributedServiceFactory::class, - AdminRoleService::class => AttributedServiceFactory::class, - AdminRepository::class => AttributedRepositoryFactory::class, - AdminRoleRepository::class => AttributedRepositoryFactory::class, - ], - 'aliases' => [ - AdminServiceInterface::class => AdminService::class, - AdminRoleServiceInterface::class => AdminRoleService::class, + AdminRepository::class => AttributedRepositoryFactory::class, + AdminLoginRepository::class => AttributedRepositoryFactory::class, + AdminRoleRepository::class => AttributedRepositoryFactory::class, ], ]; } @@ -55,10 +45,11 @@ private function getDoctrineConfig(): array 'AdminEntities' => [ 'class' => AttributeDriver::class, 'cache' => 'array', - 'paths' => getcwd() . '/src/Core/src/Admin/src/Entity', + 'paths' => [__DIR__ . '/Entity'], ], ], 'types' => [ + AdminRoleEnumType::NAME => AdminRoleEnumType::class, AdminStatusEnumType::NAME => AdminStatusEnumType::class, ], ]; diff --git a/src/Core/src/Admin/src/DBAL/Types/AdminRoleEnumType.php b/src/Core/src/Admin/src/DBAL/Types/AdminRoleEnumType.php new file mode 100644 index 00000000..3900773f --- /dev/null +++ b/src/Core/src/Admin/src/DBAL/Types/AdminRoleEnumType.php @@ -0,0 +1,23 @@ + AdminStatusEnum::Active])] + #[ORM\Column( + type: 'admin_status_enum', + enumType: AdminStatusEnum::class, + options: ['default' => AdminStatusEnum::Active] + )] protected AdminStatusEnum $status = AdminStatusEnum::Active; #[ORM\ManyToMany(targetEntity: AdminRole::class)] @@ -44,33 +51,26 @@ class Admin extends AbstractEntity implements UserEntityInterface #[ORM\InverseJoinColumn(name: 'roleUuid', referencedColumnName: 'uuid')] protected Collection $roles; + #[ORM\OneToMany(targetEntity: Setting::class, mappedBy: 'admin')] + protected Collection $settings; + public function __construct() { parent::__construct(); $this->created(); - $this->roles = new ArrayCollection(); + $this->roles = new ArrayCollection(); + $this->settings = new ArrayCollection(); } - public function getArrayCopy(): array + public function getIdentity(): ?string { - return [ - 'uuid' => $this->getUuid()->toString(), - 'identity' => $this->getIdentity(), - 'firstName' => $this->getFirstName(), - 'lastName' => $this->getLastName(), - 'status' => $this->getStatus(), - 'roles' => $this->getRoles()->map(function (AdminRole $role) { - return $role->getArrayCopy(); - })->toArray(), - 'created' => $this->getCreated(), - 'updated' => $this->getUpdated(), - ]; + return $this->identity; } - public function getIdentity(): string + public function hasIdentity(): bool { - return $this->identity; + return $this->identity !== null; } public function setIdentity(string $identity): self @@ -80,7 +80,7 @@ public function setIdentity(string $identity): self return $this; } - public function getFirstName(): string + public function getFirstName(): ?string { return $this->firstName; } @@ -92,7 +92,7 @@ public function setFirstName(string $firstName): self return $this; } - public function getLastName(): string + public function getLastName(): ?string { return $this->lastName; } @@ -104,7 +104,7 @@ public function setLastName(string $lastName): self return $this; } - public function getPassword(): string + public function getPassword(): ?string { return $this->password; } @@ -128,9 +128,9 @@ public function setStatus(AdminStatusEnum $status): self return $this; } - public function getRoles(): Collection + public function getRoles(): array { - return $this->roles; + return $this->roles->toArray(); } public function setRoles(ArrayCollection $roles): self @@ -149,6 +149,16 @@ public function addRole(RoleInterface $role): self return $this; } + public function hasRole(RoleInterface $role): bool + { + return $this->roles->contains($role); + } + + public function hasRoles(): bool + { + return $this->roles->count() > 0; + } + public function removeRole(RoleInterface $role): self { if ($this->roles->contains($role)) { @@ -165,11 +175,6 @@ public function resetRoles(): self return $this; } - public function hasRoles(): bool - { - return $this->roles->count() > 0; - } - public function activate(): self { $this->status = AdminStatusEnum::Active; @@ -191,6 +196,20 @@ public function isActive(): bool public function getIdentifier(): string { - return $this->getIdentity(); + return $this->identity; + } + + public function getArrayCopy(): array + { + return [ + 'uuid' => $this->uuid->toString(), + 'identity' => $this->identity, + 'firstName' => $this->firstName, + 'lastName' => $this->lastName, + 'status' => $this->status->value, + 'roles' => array_map(fn (AdminRole $role): array => $role->getArrayCopy(), $this->roles->toArray()), + 'created' => $this->created, + 'updated' => $this->updated, + ]; } } diff --git a/src/Core/src/Admin/src/Entity/AdminIdentity.php b/src/Core/src/Admin/src/Entity/AdminIdentity.php new file mode 100644 index 00000000..87f7bf7b --- /dev/null +++ b/src/Core/src/Admin/src/Entity/AdminIdentity.php @@ -0,0 +1,61 @@ +uuid; + } + + public function getIdentity(): string + { + return $this->identity; + } + + public function getStatus(): AdminStatusEnum + { + return $this->status; + } + + public function isActive(): bool + { + return $this->getStatus() === AdminStatusEnum::Active; + } + + public function getRoles(): iterable + { + return $this->roles; + } + + /** + * @psalm-return array + */ + public function getDetails(): array + { + return $this->details; + } + + /** + * @param mixed|null $default + */ + public function getDetail(string $name, $default = null): mixed + { + return $this->details[$name] ?? $default; + } +} diff --git a/src/Core/src/Admin/src/Entity/AdminLogin.php b/src/Core/src/Admin/src/Entity/AdminLogin.php new file mode 100644 index 00000000..55695057 --- /dev/null +++ b/src/Core/src/Admin/src/Entity/AdminLogin.php @@ -0,0 +1,301 @@ +identity; + } + + public function setIdentity(string $identity): self + { + $this->identity = $identity; + + return $this; + } + + public function getAdminIp(): ?string + { + return $this->adminIp; + } + + public function setAdminIp(?string $adminIp): self + { + $this->adminIp = $adminIp; + + return $this; + } + + public function getCountry(): ?string + { + return $this->country; + } + + public function setCountry(?string $country): self + { + $this->country = $country; + + return $this; + } + + public function getContinent(): ?string + { + return $this->continent; + } + + public function setContinent(?string $continent): self + { + $this->continent = $continent; + + return $this; + } + + public function getOrganization(): ?string + { + return $this->organization; + } + + public function setOrganization(?string $organization): self + { + $this->organization = $organization; + + return $this; + } + + public function getDeviceType(): ?string + { + return $this->deviceType; + } + + public function setDeviceType(?string $deviceType): self + { + $this->deviceType = $deviceType; + + return $this; + } + + public function getDeviceBrand(): ?string + { + return $this->deviceBrand; + } + + public function setDeviceBrand(?string $deviceBrand): self + { + $this->deviceBrand = $deviceBrand; + + return $this; + } + + public function getDeviceModel(): ?string + { + return $this->deviceModel; + } + + public function setDeviceModel(?string $deviceModel): self + { + $this->deviceModel = $deviceModel; + + return $this; + } + + public function getIsMobile(): ?YesNoEnum + { + return $this->isMobile; + } + + public function setIsMobile(?YesNoEnum $isMobile): self + { + $this->isMobile = $isMobile; + + return $this; + } + + public function getOsName(): ?string + { + return $this->osName; + } + + public function setOsName(?string $osName): self + { + $this->osName = $osName; + + return $this; + } + + public function getOsVersion(): ?string + { + return $this->osVersion; + } + + public function setOsVersion(?string $osVersion): self + { + $this->osVersion = $osVersion; + + return $this; + } + + public function getOsPlatform(): ?string + { + return $this->osPlatform; + } + + public function setOsPlatform(?string $osPlatform): self + { + $this->osPlatform = $osPlatform; + + return $this; + } + + public function getClientType(): ?string + { + return $this->clientType; + } + + public function setClientType(?string $clientType): self + { + $this->clientType = $clientType; + + return $this; + } + + public function getClientName(): ?string + { + return $this->clientName; + } + + public function setClientName(?string $clientName): self + { + $this->clientName = $clientName; + + return $this; + } + + public function getClientEngine(): ?string + { + return $this->clientEngine; + } + + public function setClientEngine(?string $clientEngine): self + { + $this->clientEngine = $clientEngine; + + return $this; + } + + public function getClientVersion(): ?string + { + return $this->clientVersion; + } + + public function setClientVersion(?string $clientVersion): self + { + $this->clientVersion = $clientVersion; + + return $this; + } + + public function getLoginStatus(): ?SuccessFailureEnum + { + return $this->loginStatus; + } + + public function setLoginStatus(?SuccessFailureEnum $loginStatus): self + { + $this->loginStatus = $loginStatus; + + return $this; + } + + public function getArrayCopy(): array + { + return [ + 'uuid' => $this->uuid->toString(), + 'identity' => $this->identity, + 'adminIp' => $this->adminIp, + 'country' => $this->country, + 'continent' => $this->continent, + 'organization' => $this->organization, + 'deviceType' => $this->deviceType, + 'deviceBrand' => $this->deviceBrand, + 'deviceModel' => $this->deviceModel, + 'isMobile' => $this->isMobile->value, + 'osName' => $this->osName, + 'osVersion' => $this->osVersion, + 'osPlatform' => $this->osPlatform, + 'clientType' => $this->clientType, + 'clientName' => $this->clientName, + 'clientEngine' => $this->clientEngine, + 'clientVersion' => $this->clientVersion, + 'loginStatus' => $this->loginStatus->value, + 'created' => $this->created, + 'updated' => $this->updated, + ]; + } +} diff --git a/src/Core/src/Admin/src/Entity/AdminRole.php b/src/Core/src/Admin/src/Entity/AdminRole.php index c93fd261..bcc72275 100644 --- a/src/Core/src/Admin/src/Entity/AdminRole.php +++ b/src/Core/src/Admin/src/Entity/AdminRole.php @@ -4,6 +4,8 @@ namespace Core\Admin\Entity; +use BackedEnum; +use Core\Admin\Enum\AdminRoleEnum; use Core\Admin\Repository\AdminRoleRepository; use Core\App\Entity\AbstractEntity; use Core\App\Entity\RoleInterface; @@ -11,21 +13,20 @@ use Doctrine\ORM\Mapping as ORM; #[ORM\Entity(repositoryClass: AdminRoleRepository::class)] -#[ORM\Table('admin_role')] +#[ORM\Table(name: 'admin_role')] #[ORM\HasLifecycleCallbacks] class AdminRole extends AbstractEntity implements RoleInterface { use TimestampsTrait; - public const ROLE_ADMIN = 'admin'; - public const ROLE_SUPERUSER = 'superuser'; - public const ROLES = [ - self::ROLE_ADMIN, - self::ROLE_SUPERUSER, - ]; - - #[ORM\Column(name: 'name', type: 'string', length: 30, unique: true)] - protected string $name = ''; + #[ORM\Column( + name: 'name', + type: 'admin_role_enum', + unique: true, + enumType: AdminRoleEnum::class, + options: ['default' => AdminRoleEnum::Admin] + )] + protected AdminRoleEnum $name = AdminRoleEnum::Admin; public function __construct() { @@ -34,12 +35,15 @@ public function __construct() $this->created(); } - public function getName(): string + public function getName(): AdminRoleEnum { return $this->name; } - public function setName(string $name): RoleInterface + /** + * @param AdminRoleEnum $name + */ + public function setName(BackedEnum $name): self { $this->name = $name; @@ -49,8 +53,10 @@ public function setName(string $name): RoleInterface public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'name' => $this->getName(), + 'uuid' => $this->uuid->toString(), + 'name' => $this->name->value, + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/Admin/src/Enum/AdminRoleEnum.php b/src/Core/src/Admin/src/Enum/AdminRoleEnum.php new file mode 100644 index 00000000..fe828b96 --- /dev/null +++ b/src/Core/src/Admin/src/Enum/AdminRoleEnum.php @@ -0,0 +1,18 @@ +value] = $enum->name; + + return $collector; + }, []); + } } diff --git a/src/Core/src/Admin/src/Repository/AdminLoginRepository.php b/src/Core/src/Admin/src/Repository/AdminLoginRepository.php new file mode 100644 index 00000000..87029d39 --- /dev/null +++ b/src/Core/src/Admin/src/Repository/AdminLoginRepository.php @@ -0,0 +1,65 @@ +getQueryBuilder() + ->select('DISTINCT adminLogin.identity') + ->from(AdminLogin::class, 'adminLogin') + ->orderBy('adminLogin.identity', 'ASC') + ->getQuery()->getResult(); + + return array_column($results, 'identity'); + } + + public function getAdminLogins(array $params = [], array $filters = []): QueryBuilder + { + $queryBuilder = $this + ->getQueryBuilder() + ->select(['login']) + ->from(AdminLogin::class, 'login'); + + if ( + array_key_exists('identity', $filters) + && is_string($filters['identity']) + && strlen($filters['identity']) > 0 + ) { + $queryBuilder + ->andWhere($queryBuilder->expr()->like('login.identity', ':search')) + ->setParameter('search', '%' . $filters['identity'] . '%'); + } + if ( + array_key_exists('status', $filters) + && is_string($filters['status']) + && strlen($filters['status']) > 0 + ) { + $queryBuilder + ->andWhere('login.loginStatus = :status') + ->setParameter('status', $filters['status']); + } + + $queryBuilder + ->orderBy($params['sort'], $params['dir']) + ->setFirstResult($params['offset']) + ->setMaxResults($params['limit']); + $queryBuilder->getQuery()->useQueryCache(true); + + return $queryBuilder; + } +} diff --git a/src/Core/src/Admin/src/Repository/AdminRepository.php b/src/Core/src/Admin/src/Repository/AdminRepository.php index f4385a4a..83c5958c 100644 --- a/src/Core/src/Admin/src/Repository/AdminRepository.php +++ b/src/Core/src/Admin/src/Repository/AdminRepository.php @@ -5,71 +5,60 @@ namespace Core\Admin\Repository; use Core\Admin\Entity\Admin; -use Core\App\Exception\BadRequestException; -use Core\App\Helper\PaginationHelper; -use Core\App\Message; -use Doctrine\ORM\EntityRepository; +use Core\App\Repository\AbstractRepository; use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; -use function in_array; -use function sprintf; +use function array_key_exists; +use function is_string; +use function strlen; -/** - * @extends EntityRepository - */ #[Entity(name: Admin::class)] -class AdminRepository extends EntityRepository +class AdminRepository extends AbstractRepository { - public function deleteAdmin(Admin $admin): void + public function getAdmins(array $params = [], array $filters = []): QueryBuilder { - $this->getEntityManager()->remove($admin); - $this->getEntityManager()->flush(); - } - - /** - * @throws BadRequestException - */ - public function getAdmins(array $params = []): QueryBuilder - { - $page = PaginationHelper::getOffsetAndLimit($params); - - $values = [ - 'admin.identity', - 'admin.firstName', - 'admin.lastName', - 'admin.status', - 'admin.created', - 'admin.updated', - ]; + $queryBuilder = $this + ->getQueryBuilder() + ->select(['admin']) + ->from(Admin::class, 'admin') + ->leftJoin('admin.roles', 'role'); - $params['order'] = $params['order'] ?? 'admin.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); + if ( + array_key_exists('identity', $filters) + && is_string($filters['identity']) + && strlen($filters['identity']) > 0 + ) { + $queryBuilder + ->andWhere($queryBuilder->expr()->like('admin.identity', ':identity')) + ->setParameter('identity', '%' . $filters['identity'] . '%'); + } + if ( + array_key_exists('status', $filters) + && is_string($filters['status']) + && strlen($filters['status']) > 0 + ) { + $queryBuilder + ->andWhere('admin.status = :status') + ->setParameter('status', $filters['status']); } - $params['dir'] = $params['dir'] ?? 'desc'; - if (! in_array($params['dir'], ['asc', 'desc'])) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + if ( + array_key_exists('role', $filters) + && is_string($filters['role']) + && strlen($filters['role']) > 0 + ) { + $queryBuilder + ->andWhere('role.name = :role') + ->setParameter('role', $filters['role']); } - $queryBuilder = $this - ->getEntityManager() - ->createQueryBuilder() - ->select(['admin']) - ->from(Admin::class, 'admin') - ->orderBy($params['order'], $params['dir']) - ->setFirstResult($page['offset']) - ->setMaxResults($page['limit']); + $queryBuilder + ->orderBy($params['sort'], $params['dir']) + ->setFirstResult($params['offset']) + ->setMaxResults($params['limit']) + ->groupBy('admin.uuid'); $queryBuilder->getQuery()->useQueryCache(true); return $queryBuilder; } - - public function saveAdmin(Admin $admin): Admin - { - $this->getEntityManager()->persist($admin); - $this->getEntityManager()->flush(); - - return $admin; - } } diff --git a/src/Core/src/Admin/src/Repository/AdminRoleRepository.php b/src/Core/src/Admin/src/Repository/AdminRoleRepository.php index 47fe66b5..4b7f73b5 100644 --- a/src/Core/src/Admin/src/Repository/AdminRoleRepository.php +++ b/src/Core/src/Admin/src/Repository/AdminRoleRepository.php @@ -5,52 +5,38 @@ namespace Core\Admin\Repository; use Core\Admin\Entity\AdminRole; -use Core\App\Exception\BadRequestException; -use Core\App\Helper\PaginationHelper; -use Core\App\Message; -use Doctrine\ORM\EntityRepository; +use Core\App\Repository\AbstractRepository; use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; -use function in_array; -use function sprintf; +use function array_key_exists; +use function is_string; +use function strlen; -/** - * @extends EntityRepository - */ #[Entity(name: AdminRole::class)] -class AdminRoleRepository extends EntityRepository +class AdminRoleRepository extends AbstractRepository { - /** - * @throws BadRequestException - */ - public function getAdminRoles(array $params = []): QueryBuilder + public function getAdminRoles(array $params = [], array $filters = []): QueryBuilder { - $page = PaginationHelper::getOffsetAndLimit($params); - - $values = [ - 'role.name', - 'role.created', - 'role.updated', - ]; - - $params['order'] = $params['order'] ?? 'role.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - $params['dir'] = $params['dir'] ?? 'desc'; - if (! in_array($params['dir'], ['asc', 'desc'])) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); - } - $queryBuilder = $this - ->getEntityManager() - ->createQueryBuilder() + ->getQueryBuilder() ->select(['role']) - ->from(AdminRole::class, 'role') - ->orderBy($params['order'], $params['dir']) - ->setFirstResult($page['offset']) - ->setMaxResults($page['limit']); + ->from(AdminRole::class, 'role'); + + if ( + array_key_exists('name', $filters) + && is_string($filters['name']) + && strlen($filters['name']) > 0 + ) { + $queryBuilder + ->andWhere('role.name = :name') + ->setParameter('name', $filters['name']); + } + + $queryBuilder + ->orderBy($params['sort'], $params['dir']) + ->setFirstResult($params['offset']) + ->setMaxResults($params['limit']); $queryBuilder->getQuery()->useQueryCache(true); return $queryBuilder; diff --git a/src/App/src/Command/RouteListCommand.php b/src/Core/src/App/src/Command/RouteListCommand.php similarity index 94% rename from src/App/src/Command/RouteListCommand.php rename to src/Core/src/App/src/Command/RouteListCommand.php index 8a7eab12..e9326573 100644 --- a/src/App/src/Command/RouteListCommand.php +++ b/src/Core/src/App/src/Command/RouteListCommand.php @@ -2,9 +2,9 @@ declare(strict_types=1); -namespace Api\App\Command; +namespace Core\App\Command; -use Api\App\RoutesDelegator; +use Core\App\ConfigProvider; use Dot\DependencyInjection\Attribute\Inject; use Fig\Http\Message\RequestMethodInterface; use Mezzio\Application; @@ -26,7 +26,7 @@ #[AsCommand( name: 'route:list', - description: 'List API routes', + description: 'List application routes', )] class RouteListCommand extends Command { @@ -101,7 +101,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int str_pad((string) $index++, 4, ' ', STR_PAD_LEFT), $route['method'], $route['name'], - str_replace(RoutesDelegator::REGEXP_UUID, '{uuid}', $route['path']), + str_replace(ConfigProvider::REGEXP_UUID, '{uuid}', $route['path']), ]); } $table->render(); diff --git a/src/Core/src/App/src/ConfigProvider.php b/src/Core/src/App/src/ConfigProvider.php index 0ec10a15..a3d93e65 100644 --- a/src/Core/src/App/src/ConfigProvider.php +++ b/src/Core/src/App/src/ConfigProvider.php @@ -4,6 +4,9 @@ namespace Core\App; +use Core\App\Command\RouteListCommand; +use Core\App\DBAL\Types\SuccessFailureEnumType; +use Core\App\DBAL\Types\YesNoEnumType; use Core\App\Factory\EntityListenerResolverFactory; use Core\App\Resolver\EntityListenerResolver; use Core\App\Service\MailService; @@ -27,6 +30,8 @@ class ConfigProvider { + public const REGEXP_UUID = '{uuid:[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}}'; + public function __invoke(): array { return [ @@ -45,6 +50,7 @@ private function getDependencies(): array 'dot-mail.service.default' => MailServiceAbstractFactory::class, EntityListenerResolver::class => EntityListenerResolverFactory::class, MailService::class => AttributedServiceFactory::class, + RouteListCommand::class => AttributedServiceFactory::class, ], 'aliases' => [ DotMailService::class => 'dot-mail.service.default', @@ -94,6 +100,8 @@ private function getDoctrineConfig(): array ], ], 'driver' => [ + // default metadata driver, aggregates all other drivers into a single one. + // Override `orm_default` only if you know what you're doing 'orm_default' => [ 'class' => MappingDriverChain::class, ], @@ -117,6 +125,8 @@ private function getDoctrineConfig(): array UuidType::NAME => UuidType::class, UuidBinaryType::NAME => UuidBinaryType::class, UuidBinaryOrderedTimeType::NAME => UuidBinaryOrderedTimeType::class, + SuccessFailureEnumType::NAME => SuccessFailureEnumType::class, + YesNoEnumType::NAME => YesNoEnumType::class, ], ]; } diff --git a/src/Core/src/App/src/DBAL/Types/AbstractEnumType.php b/src/Core/src/App/src/DBAL/Types/AbstractEnumType.php index 51265025..efa9cc23 100644 --- a/src/Core/src/App/src/DBAL/Types/AbstractEnumType.php +++ b/src/Core/src/App/src/DBAL/Types/AbstractEnumType.php @@ -8,12 +8,9 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\SQLitePlatform; use Doctrine\DBAL\Types\Type; -use InvalidArgumentException; use function array_map; -use function gettype; use function implode; -use function is_object; use function sprintf; abstract class AbstractEnumType extends Type @@ -31,28 +28,12 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st public function convertToPHPValue(mixed $value, AbstractPlatform $platform): mixed { - if ($value === null) { - return null; - } - - return $this->getEnumClass()::from($value); + return $this->getValue($value); } public function convertToDatabaseValue(mixed $value, AbstractPlatform $platform): mixed { - if ($value === null) { - return null; - } - - if (! $value instanceof BackedEnum) { - throw new InvalidArgumentException(sprintf( - 'Expected instance of %s, got %s', - $this->getEnumClass(), - is_object($value) ? $value::class : gettype($value) - )); - } - - return $value->value; + return $this->getValue($value); } /** @@ -64,4 +45,13 @@ private function getEnumValues(): array { return $this->getEnumClass()::cases(); } + + private function getValue(mixed $value): mixed + { + if (! $value instanceof BackedEnum) { + return $value; + } + + return $value->value; + } } diff --git a/src/Core/src/App/src/DBAL/Types/SuccessFailureEnumType.php b/src/Core/src/App/src/DBAL/Types/SuccessFailureEnumType.php new file mode 100644 index 00000000..4950088a --- /dev/null +++ b/src/Core/src/App/src/DBAL/Types/SuccessFailureEnumType.php @@ -0,0 +1,22 @@ +value; /** @var ArrayCollection */ protected ArrayCollection $roles; @@ -19,7 +20,7 @@ public function __construct() $this->roles = new ArrayCollection(); $this->roles->add( - (new UserRole())->setName(UserRole::ROLE_GUEST) + (new UserRole())->setName(UserRoleEnum::Guest) ); } @@ -35,9 +36,9 @@ public function setIdentity(string $identity): self return $this; } - public function getRoles(): ArrayCollection + public function getRoles(): array { - return $this->roles; + return $this->roles->toArray(); } public function setRoles(ArrayCollection $roles): self diff --git a/src/Core/src/App/src/Entity/PasswordTrait.php b/src/Core/src/App/src/Entity/PasswordTrait.php index a25529f9..30c3f382 100644 --- a/src/Core/src/App/src/Entity/PasswordTrait.php +++ b/src/Core/src/App/src/Entity/PasswordTrait.php @@ -23,8 +23,8 @@ public function hashPassword(string $password): string return password_hash($password, PASSWORD_DEFAULT); } - public static function verifyPassword(string $password, string $hash): bool + public function verifyPassword(string $password): bool { - return password_verify($password, $hash); + return password_verify($password, $this->password); } } diff --git a/src/Core/src/App/src/Entity/RoleInterface.php b/src/Core/src/App/src/Entity/RoleInterface.php index 6f744fa4..b1893e38 100644 --- a/src/Core/src/App/src/Entity/RoleInterface.php +++ b/src/Core/src/App/src/Entity/RoleInterface.php @@ -4,13 +4,14 @@ namespace Core\App\Entity; +use BackedEnum; use Ramsey\Uuid\UuidInterface; interface RoleInterface { public function getUuid(): UuidInterface; - public function getName(): ?string; + public function getName(): ?BackedEnum; - public function setName(string $name): RoleInterface; + public function setName(BackedEnum $name): RoleInterface; } diff --git a/src/Core/src/App/src/Enum/SuccessFailureEnum.php b/src/Core/src/App/src/Enum/SuccessFailureEnum.php new file mode 100644 index 00000000..7a1b5fa2 --- /dev/null +++ b/src/Core/src/App/src/Enum/SuccessFailureEnum.php @@ -0,0 +1,18 @@ +getRepository(AdminRole::class); $adminRole = $adminRoleRepository->findOneBy([ - 'name' => AdminRole::ROLE_ADMIN, + 'name' => AdminRoleEnum::Admin, ]); assert($adminRole instanceof AdminRole); $superUserRole = $adminRoleRepository->findOneBy([ - 'name' => AdminRole::ROLE_SUPERUSER, + 'name' => AdminRoleEnum::Superuser, ]); assert($superUserRole instanceof AdminRole); $admin = (new Admin()) ->setIdentity('admin') - ->usePassword('dotkernel') + ->usePassword('dotadmin') ->setFirstName('Dotkernel') ->setLastName('Admin') ->setStatus(AdminStatusEnum::Active) diff --git a/src/Core/src/App/src/Fixture/AdminRoleLoader.php b/src/Core/src/App/src/Fixture/AdminRoleLoader.php index 6626f608..6c25083e 100644 --- a/src/Core/src/App/src/Fixture/AdminRoleLoader.php +++ b/src/Core/src/App/src/Fixture/AdminRoleLoader.php @@ -5,6 +5,7 @@ namespace Core\App\Fixture; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Persistence\ObjectManager; @@ -12,11 +13,12 @@ class AdminRoleLoader implements FixtureInterface { public function load(ObjectManager $manager): void { - $adminRole = (new AdminRole())->setName(AdminRole::ROLE_ADMIN); - $manager->persist($adminRole); - - $superUserRole = (new AdminRole())->setName(AdminRole::ROLE_SUPERUSER); - $manager->persist($superUserRole); + $manager->persist( + (new AdminRole())->setName(AdminRoleEnum::Superuser) + ); + $manager->persist( + (new AdminRole())->setName(AdminRoleEnum::Admin) + ); $manager->flush(); } diff --git a/src/Core/src/App/src/Fixture/OAuthScopeLoader.php b/src/Core/src/App/src/Fixture/OAuthScopeLoader.php index f87d2a7c..96ea80eb 100644 --- a/src/Core/src/App/src/Fixture/OAuthScopeLoader.php +++ b/src/Core/src/App/src/Fixture/OAuthScopeLoader.php @@ -12,9 +12,10 @@ class OAuthScopeLoader implements FixtureInterface { public function load(ObjectManager $manager): void { - $scope = (new OAuthScope())->setScope('api'); + $manager->persist( + (new OAuthScope())->setScope('api') + ); - $manager->persist($scope); $manager->flush(); } } diff --git a/src/Core/src/App/src/Fixture/UserLoader.php b/src/Core/src/App/src/Fixture/UserLoader.php index 76d918d1..5906fc21 100644 --- a/src/Core/src/App/src/Fixture/UserLoader.php +++ b/src/Core/src/App/src/Fixture/UserLoader.php @@ -7,6 +7,7 @@ use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Common\DataFixtures\FixtureInterface; @@ -21,12 +22,12 @@ public function load(ObjectManager $manager): void $userRoleRepository = $manager->getRepository(UserRole::class); $guestRole = $userRoleRepository->findOneBy([ - 'name' => UserRole::ROLE_GUEST, + 'name' => UserRoleEnum::Guest, ]); assert($guestRole instanceof UserRole); $userRole = $userRoleRepository->findOneBy([ - 'name' => UserRole::ROLE_USER, + 'name' => UserRoleEnum::User, ]); assert($userRole instanceof UserRole); diff --git a/src/Core/src/App/src/Fixture/UserRoleLoader.php b/src/Core/src/App/src/Fixture/UserRoleLoader.php index 18143881..19f5f3ed 100644 --- a/src/Core/src/App/src/Fixture/UserRoleLoader.php +++ b/src/Core/src/App/src/Fixture/UserRoleLoader.php @@ -5,6 +5,7 @@ namespace Core\App\Fixture; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Doctrine\Common\DataFixtures\FixtureInterface; use Doctrine\Persistence\ObjectManager; @@ -12,11 +13,12 @@ class UserRoleLoader implements FixtureInterface { public function load(ObjectManager $manager): void { - $guest = (new UserRole())->setName(UserRole::ROLE_GUEST); - $manager->persist($guest); - - $user = (new UserRole())->setName(UserRole::ROLE_USER); - $manager->persist($user); + $manager->persist( + (new UserRole())->setName(UserRoleEnum::User) + ); + $manager->persist( + (new UserRole())->setName(UserRoleEnum::Guest) + ); $manager->flush(); } diff --git a/src/Core/src/App/src/Helper/PaginationHelper.php b/src/Core/src/App/src/Helper/PaginationHelper.php deleted file mode 100644 index b3219353..00000000 --- a/src/Core/src/App/src/Helper/PaginationHelper.php +++ /dev/null @@ -1,33 +0,0 @@ - 0) { - $offset = ($page - 1) * self::LIMIT; - $limit = self::LIMIT; - } else { - $offset = 0; - $limit = self::LIMIT; - } - - return [ - 'offset' => $offset, - 'limit' => $limit, - ]; - } -} diff --git a/src/Core/src/App/src/Helper/Paginator.php b/src/Core/src/App/src/Helper/Paginator.php new file mode 100644 index 00000000..c9fdcc71 --- /dev/null +++ b/src/Core/src/App/src/Helper/Paginator.php @@ -0,0 +1,128 @@ + $params + * @param non-empty-string $sort + * @return array{offset: int, limit: int, page: int, sort: string, dir: string} + */ + public static function getParams(array $params, string $sort, string $dir = 'desc'): array + { + $offset = 0; + $limit = 10; + $page = 1; + + if (array_key_exists('sort', $params) && is_string($params['sort']) && strlen($params['sort']) > 0) { + $sort = $params['sort']; + } + + if (array_key_exists('dir', $params) && in_array($params['dir'], ['asc', 'desc'], true)) { + $dir = $params['dir']; + } + + if (array_key_exists('all', $params)) { + return [ + 'offset' => $offset, + 'limit' => 1_000, + 'page' => $page, + 'sort' => $sort, + 'dir' => $dir, + ]; + } + + if (array_key_exists('limit', $params) && is_numeric($params['limit']) && $params['limit'] > 0) { + $limit = (int) $params['limit']; + } + + if (array_key_exists('offset', $params) && is_numeric($params['offset']) && $params['offset'] > 0) { + $offset = (int) $params['offset']; + $page = ($offset + $limit) / $limit; + } + + if (array_key_exists('page', $params) && is_numeric($params['page']) && $params['page'] > 0) { + $page = (int) $params['page']; + $offset = ($page - 1) * $limit; + } + + return [ + 'offset' => $offset, + 'limit' => $limit, + 'page' => $page, + 'sort' => $sort, + 'dir' => $dir, + ]; + } + + public static function wrapper(DoctrinePaginator $paginator, array $params = [], array $filters = []): array + { + $params['count'] = $paginator->count(); + $params['items'] = $paginator->getQuery()->getResult(); + $params['filters'] = $filters; + + $params['currentPage'] = (int) ceil($params['offset'] / $params['limit']) + 1; + $params['firstPage'] = 1; + $params['previousPage'] = max($params['currentPage'] - 1, 1); + $params['lastPage'] = $params['count'] > 0 + ? (int) ceil($params['count'] / $params['limit']) + : $params['firstPage']; + $params['isOutOfBounds'] = $params['currentPage'] > $params['lastPage']; + $params['nextPage'] = min($params['currentPage'] + 1, $params['lastPage']); + $params['isFirstPage'] = $params['page'] === $params['firstPage']; + $params['isLastPage'] = $params['currentPage'] === $params['lastPage']; + $params['hasPreviousPage'] = $params['currentPage'] > $params['firstPage']; + $params['hasNextPage'] = $params['currentPage'] < $params['lastPage']; + if ($params['isOutOfBounds']) { + $params['previousPage'] = max($params['lastPage'], 1); + $params['hasNextPage'] = false; + $params['isLastPage'] = true; + } + + $params['firstOffset'] = 0; + $params['previousOffset'] = max(0, $params['offset'] - $params['limit']); + $params['lastOffset'] = ($params['lastPage'] - 1) * $params['limit']; + $params['nextOffset'] = min($params['offset'] + $params['limit'], $params['lastOffset']); + if ($params['isOutOfBounds']) { + $params['previousOffset'] = $params['lastOffset']; + } + + $params['range'] = 5; + if ($params['isOutOfBounds']) { + $params['pages'] = range( + max(1, $params['lastPage'] - $params['range']), + min($params['lastPage'], $params['currentPage'] + $params['range']) + ); + } else { + $params['pages'] = range( + max(1, $params['currentPage'] - $params['range']), + min($params['lastPage'], $params['currentPage'] + $params['range']) + ); + } + + $params['queryParams'] = [ + 'filters' => $params['filters'], + 'offset' => $params['offset'], + 'limit' => $params['limit'], + 'sort' => $params['sort'], + 'dir' => $params['dir'], + ]; + + return $params; + } +} diff --git a/src/Core/src/App/src/InputFilter/AbstractInputFilter.php b/src/Core/src/App/src/InputFilter/AbstractInputFilter.php new file mode 100644 index 00000000..b38fb941 --- /dev/null +++ b/src/Core/src/App/src/InputFilter/AbstractInputFilter.php @@ -0,0 +1,14 @@ + + */ +abstract class AbstractInputFilter extends InputFilter +{ +} diff --git a/src/Core/src/App/src/Message.php b/src/Core/src/App/src/Message.php index c8bee608..8b32293d 100644 --- a/src/Core/src/App/src/Message.php +++ b/src/Core/src/App/src/Message.php @@ -4,49 +4,131 @@ namespace Core\App; +use function implode; +use function sprintf; + class Message { - public const ADMIN_CREATED = 'Admin account has been created.'; - public const ADMIN_NOT_ACTIVATED = 'This account is deactivated.'; - public const ADMIN_NOT_FOUND = 'Admin not found.'; - public const AVATAR_MISSING = 'This user account has no avatar associated with it.'; - public const DUPLICATE_EMAIL = 'An account with this email address already exists.'; - public const DUPLICATE_IDENTITY = 'An account with this identity already exists.'; - public const ERROR_REPORT_OK = 'Error report successfully saved.'; - public const ERROR_REPORT_NOT_ALLOWED = 'You are not allowed to report errors.'; - public const ERROR_REPORT_NOT_ENABLED = 'Remote error reporting is not enabled.'; - public const INVALID_CLIENT_ID = 'Invalid client_id.'; - public const INVALID_CONFIG = 'Invalid configuration value: \'%s\''; - public const INVALID_VALUE = 'The value specified for \'%s\' is invalid.'; - public const MAIL_NOT_SENT_TO = 'Could not send mail to \'%s\'.'; - public const MAIL_SENT_RECOVER_IDENTITY = 'If the provided email identifies an account in our system, ' + public const ACCOUNT_UPDATED = 'Your account was updated successfully.'; + public const ADMIN_CONFIRM_DELETION = 'Please confirm the admin deletion.'; + public const ADMIN_CREATED = 'Admin created successfully.'; + public const ADMIN_DELETED = 'Admin deleted successfully'; + public const ADMIN_INACTIVE = 'Admin account is inactive.'; + public const ADMIN_NOT_FOUND = 'Admin not found.'; + public const ADMIN_UPDATED = 'Admin updated successfully.'; + public const AN_ERROR_OCCURRED = 'An error occurred, please try again later.'; + public const DUPLICATE_EMAIL = 'An account with this email address already exists.'; + public const DUPLICATE_IDENTITY = 'An account with this identity already exists.'; + public const ERROR_REPORT_OK = 'Error report successfully saved.'; + public const ERROR_REPORT_NOT_ALLOWED = 'You are not allowed to report errors.'; + public const ERROR_REPORT_NOT_ENABLED = 'Remote error reporting is not enabled.'; + public const INVALID_CLIENT_ID = 'Invalid client_id.'; + public const INVALID_CONFIG = 'Invalid configuration value: "%s"'; + public const INVALID_CSRF = 'Invalid CSRF.'; + public const INVALID_CURRENT_PASSWORD = 'Current password is incorrect.'; + public const INVALID_VALUE = 'The value specified for "%s" is invalid.'; + public const MAIL_NOT_SENT_TO = 'Could not send mail to "%s".'; + public const MAIL_SENT_RECOVER_IDENTITY = 'If the provided email identifies an account in our system, ' . 'you will receive an email with your account\'s identity.'; - public const MAIL_SENT_RESET_PASSWORD = 'If the provided email identifies an account in our system, ' + public const MAIL_SENT_RESET_PASSWORD = 'If the provided email identifies an account in our system, ' . 'you will receive an email with further instructions on resetting your account\'s password.'; - public const MAIL_SENT_USER_ACTIVATION = 'User activation mail has been successfully sent to \'%s\''; - public const METHOD_NOT_ALLOWED = 'The request method is not supported for the requested resource.'; - public const MISSING_CONFIG = 'Missing configuration value: \'%s\'.'; - public const RESET_PASSWORD_EXPIRED = 'Password reset request for hash: \'%s\' is invalid (expired).'; - public const RESET_PASSWORD_NOT_FOUND = 'Reset password not found.'; - public const RESET_PASSWORD_OK = 'Password successfully modified.'; - public const RESET_PASSWORD_USED = 'Password reset request for hash: \'%s\' is invalid (used).'; - public const RESET_PASSWORD_VALID = 'Password reset request for hash: \'%s\' is valid.'; - public const RESOURCE_NOT_ALLOWED = 'You are not allowed to access this resource.'; - public const RESTRICTION_DEPRECATION = 'Cannot use both `%s` and `%s` attributes on the same object.'; - public const RESTRICTION_IMAGE = 'File must be an image (jpg, png).'; - public const RESTRICTION_ROLES = 'User accounts must have at least one role.'; - public const ROLE_NOT_FOUND = 'Role not found.'; - public const SERVICE_NOT_FOUND = 'Service %s not found in container.'; - public const USER_ACTIVATED = 'This account has been activated.'; - public const USER_ALREADY_ACTIVATED = 'This account is already active.'; - public const USER_ALREADY_DEACTIVATED = 'This account is already inactive.'; - public const USER_DEACTIVATED = 'This account has been deactivated.'; - public const USER_NOT_ACTIVATED = 'User account must be activated first.'; - public const USER_NOT_FOUND = 'User not found.'; - public const USER_NOT_FOUND_BY_IDENTITY = 'Could not find account by identity \'%s\''; - public const VALIDATOR_MIN_LENGTH = '%s must be at least %d characters long.'; - public const VALIDATOR_PASSWORD_MISMATCH = 'Password confirmation does not match the provided password.'; - public const VALIDATOR_REQUIRED_FIELD = 'This field is required and cannot be empty.'; - public const VALIDATOR_REQUIRED_FIELD_BY_NAME = '%s is required and cannot be empty.'; - public const VALIDATOR_REQUIRED_UPLOAD = 'A file must be uploaded first.'; + public const MAIL_SENT_USER_ACTIVATION = 'User activation mail has been successfully sent to "%s"'; + public const MISSING_CONFIG = 'Missing configuration value: "%s".'; + public const RESET_PASSWORD_EXPIRED = 'Reset password hash is invalid (expired).'; + public const RESET_PASSWORD_NOT_FOUND = 'Reset password request not found.'; + public const RESET_PASSWORD_OK = 'Password successfully modified.'; + public const RESET_PASSWORD_USED = 'Reset password hash is invalid (used).'; + public const RESET_PASSWORD_VALID = 'Reset password hash is valid.'; + public const RESOURCE_NOT_ALLOWED = 'You are not allowed to access this resource.'; + public const RESTRICTION_DEPRECATION = 'Cannot use both "%s" and "%s" attributes on the same object.'; + public const RESTRICTION_IMAGE = 'File must be an image> Accepted mim type(s): %s'; + public const RESTRICTION_ROLES = 'At least one role is required.'; + public const ROLE_NOT_FOUND = 'Role not found.'; + public const SERVICE_NOT_FOUND = 'Service %s not found in the container.'; + public const SETTING_NOT_FOUND = 'Setting "%s" not found.'; + public const USER_ACTIVATED = 'User account has been activated.'; + public const USER_ALREADY_ACTIVATED = 'User account is already active.'; + public const USER_ALREADY_DEACTIVATED = 'User account is already inactive.'; + public const USER_AVATAR_MISSING = 'User avatar not found.'; + public const USER_AVATAR_UPDATED = 'User avatar updated successfully.'; + public const USER_CONFIRM_DELETION = 'Please confirm the user deletion.'; + public const USER_CREATED = 'User created successfully.'; + public const USER_DEACTIVATED = 'User account has been deactivated.'; + public const USER_DELETED = 'User account deleted successfully.'; + public const USER_NOT_ACTIVATED = 'User account must be activated first.'; + public const USER_NOT_FOUND = 'User not found.'; + public const USER_UPDATED = 'User updated successfully.'; + public const VALIDATOR_INVALID_CHARACTERS = 'The value specified contains invalid characters.'; + public const VALIDATOR_INVALID_EMAIL = 'The value specified must be a valid email address.'; + public const VALIDATOR_LENGTH_MAX = 'The value specified must have at most %d characters.'; + public const VALIDATOR_LENGTH_MIN = 'The value specified must have at least %d characters.'; + public const VALIDATOR_LENGTH_MIN_MAX = 'The value specified must have between %d and %d characters.'; + public const VALIDATOR_MISMATCH = '"%s" and "%s" do not match.'; + public const VALIDATOR_REQUIRED_FIELD = 'This field is required and cannot be empty.'; + public const VALIDATOR_REQUIRED_UPLOAD = 'A file must be uploaded first.'; + + public static function invalidConfig(string $config): string + { + return sprintf(self::INVALID_CONFIG, $config); + } + + public static function invalidValue(string $value): string + { + return sprintf(self::INVALID_VALUE, $value); + } + + public static function missingConfig(string $config): string + { + return sprintf(self::MISSING_CONFIG, $config); + } + + public static function mailNotSentTo(string $email): string + { + return sprintf(self::MAIL_NOT_SENT_TO, $email); + } + + public static function mailSentUserActivation(string $email): string + { + return sprintf(self::MAIL_SENT_USER_ACTIVATION, $email); + } + + public static function restrictionDeprecation(string $first, string $second): string + { + return sprintf(self::RESTRICTION_DEPRECATION, $first, $second); + } + + public static function restrictionImage(array $mimeTypes): string + { + return sprintf(self::RESTRICTION_IMAGE, implode(',', $mimeTypes)); + } + + public static function serviceNotFound(string $service): string + { + return sprintf(self::SERVICE_NOT_FOUND, $service); + } + + public static function settingNotFound(string $identifier): string + { + return sprintf(self::SETTING_NOT_FOUND, $identifier); + } + + public static function validatorLengthMax(int $max): string + { + return sprintf(self::VALIDATOR_LENGTH_MAX, $max); + } + + public static function validatorLengthMin(int $min): string + { + return sprintf(self::VALIDATOR_LENGTH_MIN, $min); + } + + public static function validatorLengthMinMax(int $min, int $max): string + { + return sprintf(self::VALIDATOR_LENGTH_MIN_MAX, $min, $max); + } + + public static function validatorMismatch(string $first, string $second): string + { + return sprintf(self::VALIDATOR_MISMATCH, $first, $second); + } } diff --git a/src/Core/src/App/src/Migration/Version20241130084238.php b/src/Core/src/App/src/Migration/Version20241130084238.php deleted file mode 100644 index 0a0edcc4..00000000 --- a/src/Core/src/App/src/Migration/Version20241130084238.php +++ /dev/null @@ -1,91 +0,0 @@ -addSql('CREATE TABLE admin (uuid BINARY(16) NOT NULL, identity VARCHAR(100) NOT NULL, firstName VARCHAR(191) NOT NULL, lastName VARCHAR(191) NOT NULL, password VARCHAR(100) NOT NULL, status ENUM(\'active\', \'inactive\') DEFAULT \'active\' NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_880E0D766A95E9C4 (identity), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE admin_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_1614D53DD73087E9 (userUuid), INDEX IDX_1614D53D88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE admin_role (uuid BINARY(16) NOT NULL, name VARCHAR(30) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_7770088A5E237E06 (name), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_access_tokens (id INT UNSIGNED AUTO_INCREMENT NOT NULL, user_id VARCHAR(25) DEFAULT NULL, token VARCHAR(100) NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expires_at DATETIME NOT NULL, client_id INT UNSIGNED DEFAULT NULL, INDEX IDX_CA42527C19EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_access_token_scopes (access_token_id INT UNSIGNED NOT NULL, scope_id INT UNSIGNED NOT NULL, INDEX IDX_9FDF62E92CCB2688 (access_token_id), INDEX IDX_9FDF62E9682B5931 (scope_id), PRIMARY KEY(access_token_id, scope_id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_auth_codes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expiresDatetime DATETIME DEFAULT NULL, client_id INT UNSIGNED DEFAULT NULL, INDEX IDX_BB493F8319EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_auth_code_scopes (auth_code_id INT UNSIGNED NOT NULL, scope_id INT UNSIGNED NOT NULL, INDEX IDX_988BFFBF69FEDEE4 (auth_code_id), INDEX IDX_988BFFBF682B5931 (scope_id), PRIMARY KEY(auth_code_id, scope_id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_clients (id INT UNSIGNED AUTO_INCREMENT NOT NULL, name VARCHAR(40) NOT NULL, secret VARCHAR(100) DEFAULT NULL, redirect VARCHAR(191) NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, isConfidential TINYINT(1) DEFAULT 0 NOT NULL, user_id BINARY(16) DEFAULT NULL, INDEX IDX_13CE8101A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_refresh_tokens (id INT UNSIGNED AUTO_INCREMENT NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expires_at DATETIME NOT NULL, access_token_id INT UNSIGNED DEFAULT NULL, INDEX IDX_5AB6872CCB2688 (access_token_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE oauth_scopes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, scope VARCHAR(191) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user (uuid BINARY(16) NOT NULL, identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM(\'active\', \'pending\', \'deleted\') DEFAULT \'pending\' NOT NULL, hash VARCHAR(64) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_54FCD59FD73087E9 (userUuid), INDEX IDX_54FCD59F88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user_avatar (uuid BINARY(16) NOT NULL, name VARCHAR(191) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_73256912D73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user_detail (uuid BINARY(16) NOT NULL, firstName VARCHAR(191) DEFAULT NULL, lastName VARCHAR(191) DEFAULT NULL, email VARCHAR(191) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_4B5464AED73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user_reset_password (uuid BINARY(16) NOT NULL, expires DATETIME NOT NULL, hash VARCHAR(64) NOT NULL, status ENUM(\'completed\', \'requested\') DEFAULT \'requested\' NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_D21DE3BCD1B862B8 (hash), INDEX IDX_D21DE3BCD73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('CREATE TABLE user_role (uuid BINARY(16) NOT NULL, name VARCHAR(20) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_2DE8C6A35E237E06 (name), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4'); - $this->addSql('ALTER TABLE admin_roles ADD CONSTRAINT FK_1614D53DD73087E9 FOREIGN KEY (userUuid) REFERENCES admin (uuid)'); - $this->addSql('ALTER TABLE admin_roles ADD CONSTRAINT FK_1614D53D88446210 FOREIGN KEY (roleUuid) REFERENCES admin_role (uuid)'); - $this->addSql('ALTER TABLE oauth_access_tokens ADD CONSTRAINT FK_CA42527C19EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id)'); - $this->addSql('ALTER TABLE oauth_access_token_scopes ADD CONSTRAINT FK_9FDF62E92CCB2688 FOREIGN KEY (access_token_id) REFERENCES oauth_access_tokens (id)'); - $this->addSql('ALTER TABLE oauth_access_token_scopes ADD CONSTRAINT FK_9FDF62E9682B5931 FOREIGN KEY (scope_id) REFERENCES oauth_scopes (id)'); - $this->addSql('ALTER TABLE oauth_auth_codes ADD CONSTRAINT FK_BB493F8319EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id)'); - $this->addSql('ALTER TABLE oauth_auth_code_scopes ADD CONSTRAINT FK_988BFFBF69FEDEE4 FOREIGN KEY (auth_code_id) REFERENCES oauth_auth_codes (id)'); - $this->addSql('ALTER TABLE oauth_auth_code_scopes ADD CONSTRAINT FK_988BFFBF682B5931 FOREIGN KEY (scope_id) REFERENCES oauth_scopes (id)'); - $this->addSql('ALTER TABLE oauth_clients ADD CONSTRAINT FK_13CE8101A76ED395 FOREIGN KEY (user_id) REFERENCES user (uuid)'); - $this->addSql('ALTER TABLE oauth_refresh_tokens ADD CONSTRAINT FK_5AB6872CCB2688 FOREIGN KEY (access_token_id) REFERENCES oauth_access_tokens (id)'); - $this->addSql('ALTER TABLE user_roles ADD CONSTRAINT FK_54FCD59FD73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid)'); - $this->addSql('ALTER TABLE user_roles ADD CONSTRAINT FK_54FCD59F88446210 FOREIGN KEY (roleUuid) REFERENCES user_role (uuid)'); - $this->addSql('ALTER TABLE user_avatar ADD CONSTRAINT FK_73256912D73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid)'); - $this->addSql('ALTER TABLE user_detail ADD CONSTRAINT FK_4B5464AED73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid)'); - $this->addSql('ALTER TABLE user_reset_password ADD CONSTRAINT FK_D21DE3BCD73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE admin_roles DROP FOREIGN KEY FK_1614D53DD73087E9'); - $this->addSql('ALTER TABLE admin_roles DROP FOREIGN KEY FK_1614D53D88446210'); - $this->addSql('ALTER TABLE oauth_access_tokens DROP FOREIGN KEY FK_CA42527C19EB6921'); - $this->addSql('ALTER TABLE oauth_access_token_scopes DROP FOREIGN KEY FK_9FDF62E92CCB2688'); - $this->addSql('ALTER TABLE oauth_access_token_scopes DROP FOREIGN KEY FK_9FDF62E9682B5931'); - $this->addSql('ALTER TABLE oauth_auth_codes DROP FOREIGN KEY FK_BB493F8319EB6921'); - $this->addSql('ALTER TABLE oauth_auth_code_scopes DROP FOREIGN KEY FK_988BFFBF69FEDEE4'); - $this->addSql('ALTER TABLE oauth_auth_code_scopes DROP FOREIGN KEY FK_988BFFBF682B5931'); - $this->addSql('ALTER TABLE oauth_clients DROP FOREIGN KEY FK_13CE8101A76ED395'); - $this->addSql('ALTER TABLE oauth_refresh_tokens DROP FOREIGN KEY FK_5AB6872CCB2688'); - $this->addSql('ALTER TABLE user_roles DROP FOREIGN KEY FK_54FCD59FD73087E9'); - $this->addSql('ALTER TABLE user_roles DROP FOREIGN KEY FK_54FCD59F88446210'); - $this->addSql('ALTER TABLE user_avatar DROP FOREIGN KEY FK_73256912D73087E9'); - $this->addSql('ALTER TABLE user_detail DROP FOREIGN KEY FK_4B5464AED73087E9'); - $this->addSql('ALTER TABLE user_reset_password DROP FOREIGN KEY FK_D21DE3BCD73087E9'); - $this->addSql('DROP TABLE admin'); - $this->addSql('DROP TABLE admin_roles'); - $this->addSql('DROP TABLE admin_role'); - $this->addSql('DROP TABLE oauth_access_tokens'); - $this->addSql('DROP TABLE oauth_access_token_scopes'); - $this->addSql('DROP TABLE oauth_auth_codes'); - $this->addSql('DROP TABLE oauth_auth_code_scopes'); - $this->addSql('DROP TABLE oauth_clients'); - $this->addSql('DROP TABLE oauth_refresh_tokens'); - $this->addSql('DROP TABLE oauth_scopes'); - $this->addSql('DROP TABLE user'); - $this->addSql('DROP TABLE user_roles'); - $this->addSql('DROP TABLE user_avatar'); - $this->addSql('DROP TABLE user_detail'); - $this->addSql('DROP TABLE user_reset_password'); - $this->addSql('DROP TABLE user_role'); - } -} diff --git a/src/Core/src/App/src/Migration/Version20250407142911.php b/src/Core/src/App/src/Migration/Version20250407142911.php new file mode 100644 index 00000000..0aaae5aa --- /dev/null +++ b/src/Core/src/App/src/Migration/Version20250407142911.php @@ -0,0 +1,233 @@ +addSql(<<<'SQL' + CREATE TABLE admin (identity VARCHAR(191) NOT NULL, firstName VARCHAR(191) DEFAULT NULL, lastName VARCHAR(191) DEFAULT NULL, password VARCHAR(191) NOT NULL, status ENUM('active', 'inactive') DEFAULT 'active' NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_880E0D766A95E9C4 (identity), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE admin_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_1614D53DD73087E9 (userUuid), INDEX IDX_1614D53D88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE admin_login (identity VARCHAR(191) DEFAULT NULL, adminIp VARCHAR(191) DEFAULT NULL, country VARCHAR(191) DEFAULT NULL, continent VARCHAR(191) DEFAULT NULL, organization VARCHAR(191) DEFAULT NULL, deviceType VARCHAR(191) DEFAULT NULL, deviceBrand VARCHAR(191) DEFAULT NULL, deviceModel VARCHAR(40) DEFAULT NULL, isMobile ENUM('yes', 'no') DEFAULT NULL, osName VARCHAR(191) DEFAULT NULL, osVersion VARCHAR(191) DEFAULT NULL, osPlatform VARCHAR(191) DEFAULT NULL, clientType VARCHAR(191) DEFAULT NULL, clientName VARCHAR(191) DEFAULT NULL, clientEngine VARCHAR(191) DEFAULT NULL, clientVersion VARCHAR(191) DEFAULT NULL, loginStatus ENUM('success', 'fail') DEFAULT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE admin_role (name ENUM('admin', 'superuser') DEFAULT 'admin' NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_7770088A5E237E06 (name), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_access_tokens (id INT UNSIGNED AUTO_INCREMENT NOT NULL, user_id VARCHAR(25) DEFAULT NULL, token VARCHAR(100) NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expires_at DATETIME NOT NULL, client_id INT UNSIGNED DEFAULT NULL, INDEX IDX_CA42527C19EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_access_token_scopes (access_token_id INT UNSIGNED NOT NULL, scope_id INT UNSIGNED NOT NULL, INDEX IDX_9FDF62E92CCB2688 (access_token_id), INDEX IDX_9FDF62E9682B5931 (scope_id), PRIMARY KEY(access_token_id, scope_id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_auth_codes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expiresDatetime DATETIME DEFAULT NULL, client_id INT UNSIGNED DEFAULT NULL, INDEX IDX_BB493F8319EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_auth_code_scopes (auth_code_id INT UNSIGNED NOT NULL, scope_id INT UNSIGNED NOT NULL, INDEX IDX_988BFFBF69FEDEE4 (auth_code_id), INDEX IDX_988BFFBF682B5931 (scope_id), PRIMARY KEY(auth_code_id, scope_id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_clients (id INT UNSIGNED AUTO_INCREMENT NOT NULL, name VARCHAR(40) NOT NULL, secret VARCHAR(100) DEFAULT NULL, redirect VARCHAR(191) NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, isConfidential TINYINT(1) DEFAULT 0 NOT NULL, user_id BINARY(16) DEFAULT NULL, INDEX IDX_13CE8101A76ED395 (user_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_refresh_tokens (id INT UNSIGNED AUTO_INCREMENT NOT NULL, revoked TINYINT(1) DEFAULT 0 NOT NULL, expires_at DATETIME NOT NULL, access_token_id INT UNSIGNED DEFAULT NULL, INDEX IDX_5AB6872CCB2688 (access_token_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE oauth_scopes (id INT UNSIGNED AUTO_INCREMENT NOT NULL, scope VARCHAR(191) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE settings (identifier ENUM('table_admin_list_selected_columns', 'table_admin_list_logins_selected_columns', 'table_user_list_selected_columns') NOT NULL, value LONGTEXT NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, admin_uuid BINARY(16) DEFAULT NULL, INDEX IDX_E545A0C5F166D246 (admin_uuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user (identity VARCHAR(191) NOT NULL, password VARCHAR(191) NOT NULL, status ENUM('active', 'pending', 'deleted') DEFAULT 'pending' NOT NULL, hash VARCHAR(191) NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D6496A95E9C4 (identity), UNIQUE INDEX UNIQ_8D93D649D1B862B8 (hash), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user_roles (userUuid BINARY(16) NOT NULL, roleUuid BINARY(16) NOT NULL, INDEX IDX_54FCD59FD73087E9 (userUuid), INDEX IDX_54FCD59F88446210 (roleUuid), PRIMARY KEY(userUuid, roleUuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user_avatar (name VARCHAR(191) NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_73256912D73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user_detail (firstName VARCHAR(191) DEFAULT NULL, lastName VARCHAR(191) DEFAULT NULL, email VARCHAR(191) NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_4B5464AED73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user_reset_password (expires DATETIME NOT NULL, hash VARCHAR(191) NOT NULL, status ENUM('completed', 'requested') DEFAULT 'requested' NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, userUuid BINARY(16) DEFAULT NULL, UNIQUE INDEX UNIQ_D21DE3BCD1B862B8 (hash), INDEX IDX_D21DE3BCD73087E9 (userUuid), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + CREATE TABLE user_role (name ENUM('guest', 'user') DEFAULT 'user' NOT NULL, uuid BINARY(16) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_2DE8C6A35E237E06 (name), PRIMARY KEY(uuid)) DEFAULT CHARACTER SET utf8mb4 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE admin_roles ADD CONSTRAINT FK_1614D53DD73087E9 FOREIGN KEY (userUuid) REFERENCES admin (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE admin_roles ADD CONSTRAINT FK_1614D53D88446210 FOREIGN KEY (roleUuid) REFERENCES admin_role (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_tokens ADD CONSTRAINT FK_CA42527C19EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_token_scopes ADD CONSTRAINT FK_9FDF62E92CCB2688 FOREIGN KEY (access_token_id) REFERENCES oauth_access_tokens (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_token_scopes ADD CONSTRAINT FK_9FDF62E9682B5931 FOREIGN KEY (scope_id) REFERENCES oauth_scopes (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_codes ADD CONSTRAINT FK_BB493F8319EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_code_scopes ADD CONSTRAINT FK_988BFFBF69FEDEE4 FOREIGN KEY (auth_code_id) REFERENCES oauth_auth_codes (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_code_scopes ADD CONSTRAINT FK_988BFFBF682B5931 FOREIGN KEY (scope_id) REFERENCES oauth_scopes (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_clients ADD CONSTRAINT FK_13CE8101A76ED395 FOREIGN KEY (user_id) REFERENCES user (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_refresh_tokens ADD CONSTRAINT FK_5AB6872CCB2688 FOREIGN KEY (access_token_id) REFERENCES oauth_access_tokens (id) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE settings ADD CONSTRAINT FK_E545A0C5F166D246 FOREIGN KEY (admin_uuid) REFERENCES admin (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_roles ADD CONSTRAINT FK_54FCD59FD73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_roles ADD CONSTRAINT FK_54FCD59F88446210 FOREIGN KEY (roleUuid) REFERENCES user_role (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_avatar ADD CONSTRAINT FK_73256912D73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_detail ADD CONSTRAINT FK_4B5464AED73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid) + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_reset_password ADD CONSTRAINT FK_D21DE3BCD73087E9 FOREIGN KEY (userUuid) REFERENCES user (uuid) + SQL); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql(<<<'SQL' + ALTER TABLE admin_roles DROP FOREIGN KEY FK_1614D53DD73087E9 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE admin_roles DROP FOREIGN KEY FK_1614D53D88446210 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_tokens DROP FOREIGN KEY FK_CA42527C19EB6921 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_token_scopes DROP FOREIGN KEY FK_9FDF62E92CCB2688 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_access_token_scopes DROP FOREIGN KEY FK_9FDF62E9682B5931 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_codes DROP FOREIGN KEY FK_BB493F8319EB6921 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_code_scopes DROP FOREIGN KEY FK_988BFFBF69FEDEE4 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_auth_code_scopes DROP FOREIGN KEY FK_988BFFBF682B5931 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_clients DROP FOREIGN KEY FK_13CE8101A76ED395 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE oauth_refresh_tokens DROP FOREIGN KEY FK_5AB6872CCB2688 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE settings DROP FOREIGN KEY FK_E545A0C5F166D246 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_roles DROP FOREIGN KEY FK_54FCD59FD73087E9 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_roles DROP FOREIGN KEY FK_54FCD59F88446210 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_avatar DROP FOREIGN KEY FK_73256912D73087E9 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_detail DROP FOREIGN KEY FK_4B5464AED73087E9 + SQL); + $this->addSql(<<<'SQL' + ALTER TABLE user_reset_password DROP FOREIGN KEY FK_D21DE3BCD73087E9 + SQL); + $this->addSql(<<<'SQL' + DROP TABLE admin + SQL); + $this->addSql(<<<'SQL' + DROP TABLE admin_roles + SQL); + $this->addSql(<<<'SQL' + DROP TABLE admin_login + SQL); + $this->addSql(<<<'SQL' + DROP TABLE admin_role + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_access_tokens + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_access_token_scopes + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_auth_codes + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_auth_code_scopes + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_clients + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_refresh_tokens + SQL); + $this->addSql(<<<'SQL' + DROP TABLE oauth_scopes + SQL); + $this->addSql(<<<'SQL' + DROP TABLE settings + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user_roles + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user_avatar + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user_detail + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user_reset_password + SQL); + $this->addSql(<<<'SQL' + DROP TABLE user_role + SQL); + } +} diff --git a/src/Core/src/App/src/Repository/AbstractRepository.php b/src/Core/src/App/src/Repository/AbstractRepository.php new file mode 100644 index 00000000..47177186 --- /dev/null +++ b/src/Core/src/App/src/Repository/AbstractRepository.php @@ -0,0 +1,32 @@ + + */ +abstract class AbstractRepository extends EntityRepository +{ + public function deleteResource(EntityInterface $resource): void + { + $this->getEntityManager()->remove($resource); + $this->getEntityManager()->flush(); + } + + public function getQueryBuilder(): QueryBuilder + { + return $this->getEntityManager()->createQueryBuilder(); + } + + public function saveResource(EntityInterface $resource): void + { + $this->getEntityManager()->persist($resource); + $this->getEntityManager()->flush(); + } +} diff --git a/src/Core/src/App/src/Service/AuthenticationServiceInterface.php b/src/Core/src/App/src/Service/AuthenticationServiceInterface.php new file mode 100644 index 00000000..59006d39 --- /dev/null +++ b/src/Core/src/App/src/Service/AuthenticationServiceInterface.php @@ -0,0 +1,20 @@ + [ 'class' => AttributeDriver::class, 'cache' => 'array', - 'paths' => getcwd() . '/src/Core/src/Security/src/Entity', + 'paths' => [__DIR__ . '/Entity'], ], ], ]; diff --git a/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php b/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php index 1a09230d..3cd6b17c 100644 --- a/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthAccessTokenRepository.php @@ -5,21 +5,18 @@ namespace Core\Security\Repository; use Core\Admin\Entity\Admin; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthAccessToken; use Core\Security\Entity\OAuthClient; use Core\User\Entity\User; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\AccessTokenEntityInterface; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\UserEntityInterface; use League\OAuth2\Server\Repositories\AccessTokenRepositoryInterface; -/** - * @extends EntityRepository - */ #[Entity(name: OAuthAccessToken::class)] -class OAuthAccessTokenRepository extends EntityRepository implements AccessTokenRepositoryInterface +class OAuthAccessTokenRepository extends AbstractRepository implements AccessTokenRepositoryInterface { /** * @return OAuthAccessToken[] @@ -27,8 +24,7 @@ class OAuthAccessTokenRepository extends EntityRepository implements AccessToken public function findAccessTokens(string $identifier): array { return $this - ->getEntityManager() - ->createQueryBuilder() + ->getQueryBuilder() ->select(['oauth_access_tokens']) ->from(OAuthAccessToken::class, 'oauth_access_tokens') ->andWhere('oauth_access_tokens.userId = :identifier') diff --git a/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php b/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php index 2628d3ad..a606ee0c 100644 --- a/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthAuthCodeRepository.php @@ -4,17 +4,14 @@ namespace Core\Security\Repository; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthAuthCode; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\AuthCodeEntityInterface; use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; -/** - * @extends EntityRepository - */ #[Entity(name: OAuthAuthCode::class)] -class OAuthAuthCodeRepository extends EntityRepository implements AuthCodeRepositoryInterface +class OAuthAuthCodeRepository extends AbstractRepository implements AuthCodeRepositoryInterface { public function getNewAuthCode(): OAuthAuthCode { diff --git a/src/Core/src/Security/src/Repository/OAuthClientRepository.php b/src/Core/src/Security/src/Repository/OAuthClientRepository.php index e7b4ad55..53dd3624 100644 --- a/src/Core/src/Security/src/Repository/OAuthClientRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthClientRepository.php @@ -4,8 +4,8 @@ namespace Core\Security\Repository; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthClient; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Repositories\ClientRepositoryInterface; @@ -13,11 +13,8 @@ use function in_array; use function password_verify; -/** - * @extends EntityRepository - */ #[Entity(name: OAuthClient::class)] -class OAuthClientRepository extends EntityRepository implements ClientRepositoryInterface +class OAuthClientRepository extends AbstractRepository implements ClientRepositoryInterface { private const GRANT_TYPE_CLIENT_CREDENTIALS = 'client_credentials'; private const GRANT_TYPE_AUTHORIZATION_CODE = 'authorization_code'; diff --git a/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php b/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php index c2b74c54..07b14148 100644 --- a/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthRefreshTokenRepository.php @@ -4,17 +4,14 @@ namespace Core\Security\Repository; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthRefreshToken; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\RefreshTokenEntityInterface; use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; -/** - * @extends EntityRepository - */ #[Entity(name: OAuthRefreshToken::class)] -class OAuthRefreshTokenRepository extends EntityRepository implements RefreshTokenRepositoryInterface +class OAuthRefreshTokenRepository extends AbstractRepository implements RefreshTokenRepositoryInterface { public function getNewRefreshToken(): OAuthRefreshToken { diff --git a/src/Core/src/Security/src/Repository/OAuthScopeRepository.php b/src/Core/src/Security/src/Repository/OAuthScopeRepository.php index ded71369..a78c4ae5 100644 --- a/src/Core/src/Security/src/Repository/OAuthScopeRepository.php +++ b/src/Core/src/Security/src/Repository/OAuthScopeRepository.php @@ -4,18 +4,15 @@ namespace Core\Security\Repository; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthScope; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\ClientEntityInterface; use League\OAuth2\Server\Entities\ScopeEntityInterface; use League\OAuth2\Server\Repositories\ScopeRepositoryInterface; -/** - * @extends EntityRepository - */ #[Entity(name: OAuthScope::class)] -class OAuthScopeRepository extends EntityRepository implements ScopeRepositoryInterface +class OAuthScopeRepository extends AbstractRepository implements ScopeRepositoryInterface { /** * @param string $identifier diff --git a/src/Core/src/Setting/src/ConfigProvider.php b/src/Core/src/Setting/src/ConfigProvider.php new file mode 100644 index 00000000..c82dea1b --- /dev/null +++ b/src/Core/src/Setting/src/ConfigProvider.php @@ -0,0 +1,51 @@ + $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), + ]; + } + + public function getDependencies(): array + { + return [ + 'factories' => [ + SettingRepository::class => AttributedRepositoryFactory::class, + ], + ]; + } + + public function getDoctrineConfig(): array + { + return [ + 'driver' => [ + 'orm_default' => [ + 'drivers' => [ + 'Core\Setting\Entity' => 'SettingEntities', + ], + ], + 'SettingEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => [__DIR__ . '/Entity'], + ], + ], + 'types' => [ + SettingIdentifierEnumType::NAME => SettingIdentifierEnumType::class, + ], + ]; + } +} diff --git a/src/Core/src/Setting/src/DBAL/Types/SettingIdentifierEnumType.php b/src/Core/src/Setting/src/DBAL/Types/SettingIdentifierEnumType.php new file mode 100644 index 00000000..e5e0f93f --- /dev/null +++ b/src/Core/src/Setting/src/DBAL/Types/SettingIdentifierEnumType.php @@ -0,0 +1,23 @@ +setAdmin($admin); + $this->setIdentifier($identifier); + $this->setValue($value); + } + + public function getAdmin(): ?Admin + { + return $this->admin; + } + + public function setAdmin(Admin $admin): self + { + $this->admin = $admin; + + return $this; + } + + public function getIdentifier(): ?SettingIdentifierEnum + { + return $this->identifier; + } + + public function setIdentifier(SettingIdentifierEnum $identifier): self + { + $this->identifier = $identifier; + + return $this; + } + + public function getValue(): mixed + { + return json_decode($this->value, true); + } + + public function setValue(array $value): self + { + $this->value = json_encode(array_unique($value)); + + return $this; + } + + public function getArrayCopy(): array + { + return [ + 'identifier' => $this->identifier->value, + 'value' => $this->getValue(), + ]; + } +} diff --git a/src/Core/src/Setting/src/Enum/SettingIdentifierEnum.php b/src/Core/src/Setting/src/Enum/SettingIdentifierEnum.php new file mode 100644 index 00000000..c118510e --- /dev/null +++ b/src/Core/src/Setting/src/Enum/SettingIdentifierEnum.php @@ -0,0 +1,19 @@ + [ - UserAvatarServiceInterface::class => UserAvatarService::class, - UserResetPasswordServiceInterface::class => UserResetPasswordService::class, - UserRoleServiceInterface::class => UserRoleService::class, - UserServiceInterface::class => UserService::class, - ], 'factories' => [ UserAvatarEventListener::class => AttributedServiceFactory::class, - UserAvatarService::class => AttributedServiceFactory::class, - UserResetPasswordService::class => AttributedServiceFactory::class, - UserRoleService::class => AttributedServiceFactory::class, - UserService::class => AttributedServiceFactory::class, UserAvatarRepository::class => AttributedRepositoryFactory::class, UserDetailRepository::class => AttributedRepositoryFactory::class, UserRepository::class => AttributedRepositoryFactory::class, @@ -72,10 +53,11 @@ private function getDoctrineConfig(): array 'UserEntities' => [ 'class' => AttributeDriver::class, 'cache' => 'array', - 'paths' => getcwd() . '/src/Core/src/User/src/Entity', + 'paths' => [__DIR__ . '/Entity'], ], ], 'types' => [ + UserRoleEnumType::NAME => UserRoleEnumType::class, UserStatusEnumType::NAME => UserStatusEnumType::class, UserResetPasswordStatusEnumType::NAME => UserResetPasswordStatusEnumType::class, ], diff --git a/src/Core/src/User/src/DBAL/Types/UserRoleEnumType.php b/src/Core/src/User/src/DBAL/Types/UserRoleEnumType.php new file mode 100644 index 00000000..2b8d0f51 --- /dev/null +++ b/src/Core/src/User/src/DBAL/Types/UserRoleEnumType.php @@ -0,0 +1,23 @@ + UserStatusEnum::Pending])] + #[ORM\Column( + type: 'user_status_enum', + enumType: UserStatusEnum::class, + options: ['default' => UserStatusEnum::Pending] + )] protected UserStatusEnum $status = UserStatusEnum::Pending; - #[ORM\Column(name: 'hash', type: 'string', length: 64, unique: true)] - protected string $hash; + #[ORM\Column(name: 'hash', type: 'string', length: 191, unique: true)] + protected ?string $hash = null; public function __construct() { @@ -65,92 +70,87 @@ public function __construct() $this->renewHash(); } - public function getIdentity(): string - { - return $this->identity; - } - - public function setIdentity(string $identity): self + public function getAvatar(): ?UserAvatar { - $this->identity = $identity; - return $this; + return $this->avatar; } - public function getPassword(): string + public function hasAvatar(): bool { - return $this->password; + return $this->avatar instanceof UserAvatar; } - public function setPassword(string $password): self + public function removeAvatar(): self { - $this->password = $password; + $this->avatar = null; return $this; } - public function getStatus(): UserStatusEnum - { - return $this->status; - } - - public function setStatus(UserStatusEnum $status): self + public function setAvatar(?UserAvatar $avatar): self { - $this->status = $status; + $this->avatar = $avatar; return $this; } - public function getHash(): string + public function getDetail(): UserDetail { - return $this->hash; + return $this->detail; } - public function setHash(string $hash): self + public function hasDetail(): bool { - $this->hash = $hash; - - return $this; + return $this->detail !== null; } - public function getAvatar(): ?UserAvatar + public function setDetail(UserDetail $detail): self { - return $this->avatar; + $this->detail = $detail; + + return $this; } - public function setAvatar(?UserAvatar $avatar): self + public function addResetPassword(UserResetPassword $resetPassword): void { - $this->avatar = $avatar; - - return $this; + $this->resetPasswords->add($resetPassword); } - public function removeAvatar(): self + public function createResetPassword(): self { - $this->avatar = null; + $this->resetPasswords->add( + (new UserResetPassword()) + ->setHash(self::generateHash()) + ->setUser($this) + ); return $this; } - public function hasAvatar(): bool + public function getResetPasswords(): Collection { - return $this->avatar instanceof UserAvatar; + return $this->resetPasswords; } - public function getDetail(): UserDetail + public function hasResetPassword(UserResetPassword $resetPassword): bool { - return $this->detail; + return $this->resetPasswords->contains($resetPassword); } - public function setDetail(UserDetail $detail): self + public function removeResetPassword(UserResetPassword $resetPassword): self { - $this->detail = $detail; + $this->resetPasswords->removeElement($resetPassword); return $this; } - public function getIdentifier(): string + public function setResetPasswords(array $resetPasswords): self { - return $this->getIdentity(); + foreach ($resetPasswords as $resetPassword) { + $this->resetPasswords->add($resetPassword); + } + + return $this; } public function addRole(RoleInterface $role): self @@ -160,9 +160,9 @@ public function addRole(RoleInterface $role): self return $this; } - public function getRoles(): Collection + public function getRoles(): array { - return $this->roles; + return $this->roles->toArray(); } public function hasRole(RoleInterface $role): bool @@ -186,48 +186,64 @@ public function setRoles(array $roles): self return $this; } - public function addResetPassword(UserResetPassword $resetPassword): void + public function getIdentity(): ?string { - $this->resetPasswords->add($resetPassword); + return $this->identity; } - public function createResetPassword(): self + public function hasIdentity(): bool { - $this->resetPasswords->add( - (new UserResetPassword()) - ->setHash(self::generateHash()) - ->setUser($this) - ); + return $this->identity !== null; + } + + public function setIdentity(string $identity): self + { + $this->identity = $identity; return $this; } - public function getResetPasswords(): Collection + public function getPassword(): ?string { - return $this->resetPasswords; + return $this->password; } - public function hasResetPassword(UserResetPassword $resetPassword): bool + public function setPassword(string $password): self { - return $this->resetPasswords->contains($resetPassword); + $this->password = $password; + + return $this; } - public function removeResetPassword(UserResetPassword $resetPassword): self + public function getStatus(): UserStatusEnum { - $this->resetPasswords->removeElement($resetPassword); + return $this->status; + } + + public function setStatus(UserStatusEnum $status): self + { + $this->status = $status; return $this; } - public function setResetPasswords(array $resetPasswords): self + public function getHash(): ?string { - foreach ($resetPasswords as $resetPassword) { - $this->resetPasswords->add($resetPassword); - } + return $this->hash; + } + + public function setHash(string $hash): self + { + $this->hash = $hash; return $this; } + public function getIdentifier(): string + { + return $this->identity; + } + public function activate(): self { return $this->setStatus(UserStatusEnum::Active); @@ -245,7 +261,7 @@ public static function generateHash(): string public function getName(): string { - return $this->getDetail()->getFirstName() . ' User.php' . $this->getDetail()->getLastName(); + return $this->getDetail()->getFirstName() . ' ' . $this->getDetail()->getLastName(); } public function isActive(): bool @@ -282,28 +298,18 @@ public function hasRoles(): bool return $this->roles->count() > 0; } - public function hasEmail(): bool - { - return ! empty($this->getDetail()->getEmail()); - } - public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'hash' => $this->getHash(), - 'identity' => $this->getIdentity(), - 'status' => $this->getStatus(), - 'avatar' => $this->getAvatar()?->getArrayCopy(), - 'detail' => $this->getDetail()->getArrayCopy(), - 'roles' => $this->getRoles()->map(function (UserRole $userRole) { - return $userRole->getArrayCopy(); - })->toArray(), - 'resetPasswords' => $this->getResetPasswords()->map(function (UserResetPassword $resetPassword) { - return $resetPassword->getArrayCopy(); - })->toArray(), - 'created' => $this->getCreated(), - 'updated' => $this->getUpdated(), + 'uuid' => $this->uuid->toString(), + 'avatar' => $this->avatar?->getArrayCopy(), + 'detail' => $this->detail->getArrayCopy(), + 'hash' => $this->hash, + 'identity' => $this->identity, + 'status' => $this->status->value, + 'roles' => array_map(fn (UserRole $role): array => $role->getArrayCopy(), $this->roles->toArray()), + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/User/src/Entity/UserAvatar.php b/src/Core/src/User/src/Entity/UserAvatar.php index e1b8c2b6..4bf15592 100644 --- a/src/Core/src/User/src/Entity/UserAvatar.php +++ b/src/Core/src/User/src/Entity/UserAvatar.php @@ -20,10 +20,10 @@ class UserAvatar extends AbstractEntity #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'avatar')] #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] - protected User $user; + protected ?User $user = null; #[ORM\Column(name: 'name', type: 'string', length: 191)] - protected string $name; + protected ?string $name = null; protected ?string $url = null; @@ -34,24 +34,24 @@ public function __construct() $this->created(); } - public function getUser(): User + public function getUser(): ?User { return $this->user; } - public function setUser(User $user): UserAvatar + public function setUser(User $user): self { $this->user = $user; return $this; } - public function getName(): string + public function getName(): ?string { return $this->name; } - public function setName(string $name): UserAvatar + public function setName(string $name): self { $this->name = $name; @@ -63,7 +63,7 @@ public function getUrl(): ?string return $this->url; } - public function setUrl(string $url): UserAvatar + public function setUrl(string $url): self { $this->url = $url; @@ -73,10 +73,10 @@ public function setUrl(string $url): UserAvatar public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'url' => $this->getUrl(), - 'created' => $this->getCreated(), - 'updated' => $this->getUpdated(), + 'uuid' => $this->uuid->toString(), + 'url' => $this->url, + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/User/src/Entity/UserDetail.php b/src/Core/src/User/src/Entity/UserDetail.php index dcf61187..024b91e4 100644 --- a/src/Core/src/User/src/Entity/UserDetail.php +++ b/src/Core/src/User/src/Entity/UserDetail.php @@ -18,7 +18,7 @@ class UserDetail extends AbstractEntity #[ORM\OneToOne(targetEntity: User::class, inversedBy: 'detail')] #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] - protected User $user; + protected ?User $user = null; #[ORM\Column(name: 'firstName', type: 'string', length: 191, nullable: true)] protected ?string $firstName = null; @@ -27,7 +27,7 @@ class UserDetail extends AbstractEntity protected ?string $lastName = null; #[ORM\Column(name: 'email', type: 'string', length: 191)] - protected string $email; + protected ?string $email = null; public function __construct() { @@ -36,7 +36,7 @@ public function __construct() $this->created(); } - public function getUser(): User + public function getUser(): ?User { return $this->user; } @@ -72,11 +72,16 @@ public function setLastName(?string $lastName): self return $this; } - public function getEmail(): string + public function getEmail(): ?string { return $this->email; } + public function hasEmail(): bool + { + return $this->email !== null && $this->email !== ''; + } + public function setEmail(string $email): self { $this->email = $email; @@ -87,12 +92,12 @@ public function setEmail(string $email): self public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'firstName' => $this->getFirstName(), - 'lastName' => $this->getLastName(), - 'email' => $this->getEmail(), - 'created' => $this->getCreated(), - 'updated' => $this->getUpdated(), + 'uuid' => $this->uuid->toString(), + 'firstName' => $this->firstName, + 'lastName' => $this->lastName, + 'email' => $this->email, + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/User/src/Entity/UserResetPassword.php b/src/Core/src/User/src/Entity/UserResetPassword.php index 8ee4ff7c..63c66bd4 100644 --- a/src/Core/src/User/src/Entity/UserResetPassword.php +++ b/src/Core/src/User/src/Entity/UserResetPassword.php @@ -12,7 +12,7 @@ use DateTime; use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; -use Exception; +use Throwable; #[ORM\Entity(repositoryClass: UserResetPasswordRepository::class)] #[ORM\Table(name: 'user_reset_password')] @@ -23,16 +23,17 @@ class UserResetPassword extends AbstractEntity #[ORM\ManyToOne(targetEntity: User::class, cascade: ['persist', 'remove'], inversedBy: 'resetPasswords')] #[ORM\JoinColumn(name: 'userUuid', referencedColumnName: 'uuid')] - protected User $user; + protected ?User $user = null; #[ORM\Column(name: 'expires', type: 'datetime_immutable')] - protected DateTimeImmutable $expires; + protected ?DateTimeImmutable $expires = null; - #[ORM\Column(name: 'hash', type: 'string', length: 64, unique: true)] - protected string $hash; + #[ORM\Column(name: 'hash', type: 'string', length: 191, unique: true)] + protected ?string $hash = null; #[ORM\Column( type: 'user_reset_password_status_enum', + enumType: UserResetPasswordStatusEnum::class, options: ['default' => UserResetPasswordStatusEnum::Requested], )] protected UserResetPasswordStatusEnum $status = UserResetPasswordStatusEnum::Requested; @@ -47,7 +48,7 @@ public function __construct() ); } - public function getUser(): User + public function getUser(): ?User { return $this->user; } @@ -59,7 +60,7 @@ public function setUser(User $user): self return $this; } - public function getExpires(): DateTimeImmutable + public function getExpires(): ?DateTimeImmutable { return $this->expires; } @@ -71,7 +72,7 @@ public function setExpires(DateTimeImmutable $expires): self return $this; } - public function getHash(): string + public function getHash(): ?string { return $this->hash; } @@ -104,7 +105,7 @@ public function isValid(): bool { try { return $this->getExpires() > new DateTimeImmutable(); - } catch (Exception) { + } catch (Throwable) { return false; } } @@ -119,12 +120,12 @@ public function markAsCompleted(): self public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'expires' => $this->getExpires(), - 'hash' => $this->getHash(), - 'status' => $this->getStatus(), - 'created' => $this->getCreated(), - 'updated' => $this->getUpdated(), + 'uuid' => $this->uuid->toString(), + 'expires' => $this->expires, + 'hash' => $this->hash, + 'status' => $this->status->value, + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/User/src/Entity/UserRole.php b/src/Core/src/User/src/Entity/UserRole.php index dab89330..719614a5 100644 --- a/src/Core/src/User/src/Entity/UserRole.php +++ b/src/Core/src/User/src/Entity/UserRole.php @@ -4,9 +4,11 @@ namespace Core\User\Entity; +use BackedEnum; use Core\App\Entity\AbstractEntity; use Core\App\Entity\RoleInterface; use Core\App\Entity\TimestampsTrait; +use Core\User\Enum\UserRoleEnum; use Core\User\Repository\UserRoleRepository; use Doctrine\ORM\Mapping as ORM; @@ -17,15 +19,14 @@ class UserRole extends AbstractEntity implements RoleInterface { use TimestampsTrait; - public const ROLE_GUEST = 'guest'; - public const ROLE_USER = 'user'; - public const ROLES = [ - self::ROLE_GUEST, - self::ROLE_USER, - ]; - - #[ORM\Column(name: 'name', type: 'string', length: 20, unique: true)] - protected ?string $name = null; + #[ORM\Column( + name: 'name', + type: 'user_role_enum', + unique: true, + enumType: UserRoleEnum::class, + options: ['default' => UserRoleEnum::User] + )] + protected UserRoleEnum $name = UserRoleEnum::User; public function __construct() { @@ -34,12 +35,15 @@ public function __construct() $this->created(); } - public function getName(): ?string + public function getName(): ?UserRoleEnum { return $this->name; } - public function setName(string $name): RoleInterface + /** + * @param UserRoleEnum $name + */ + public function setName(BackedEnum $name): RoleInterface { $this->name = $name; @@ -49,8 +53,10 @@ public function setName(string $name): RoleInterface public function getArrayCopy(): array { return [ - 'uuid' => $this->getUuid()->toString(), - 'name' => $this->getName(), + 'uuid' => $this->uuid->toString(), + 'name' => $this->name->value, + 'created' => $this->created, + 'updated' => $this->updated, ]; } } diff --git a/src/Core/src/User/src/Enum/UserRoleEnum.php b/src/Core/src/User/src/Enum/UserRoleEnum.php new file mode 100644 index 00000000..b08516b9 --- /dev/null +++ b/src/Core/src/User/src/Enum/UserRoleEnum.php @@ -0,0 +1,18 @@ + $value !== self::Guest); + } +} diff --git a/src/Core/src/User/src/Enum/UserStatusEnum.php b/src/Core/src/User/src/Enum/UserStatusEnum.php index 8972ce32..9ed5c057 100644 --- a/src/Core/src/User/src/Enum/UserStatusEnum.php +++ b/src/Core/src/User/src/Enum/UserStatusEnum.php @@ -4,9 +4,32 @@ namespace Core\User\Enum; +use function array_column; +use function array_filter; +use function array_reduce; + enum UserStatusEnum: string { case Active = 'active'; case Pending = 'pending'; case Deleted = 'deleted'; + + public static function values(): array + { + return array_column(self::validCases(), 'value'); + } + + public static function validCases(): array + { + return array_filter(self::cases(), fn (self $enum) => $enum !== self::Deleted); + } + + public static function toArray(): array + { + return array_reduce(self::validCases(), function (array $collector, self $enum): array { + $collector[$enum->value] = $enum->name; + + return $collector; + }, []); + } } diff --git a/src/Core/src/User/src/Repository/UserAvatarRepository.php b/src/Core/src/User/src/Repository/UserAvatarRepository.php index 9f17abec..5cb3ab91 100644 --- a/src/Core/src/User/src/Repository/UserAvatarRepository.php +++ b/src/Core/src/User/src/Repository/UserAvatarRepository.php @@ -4,27 +4,11 @@ namespace Core\User\Repository; +use Core\App\Repository\AbstractRepository; use Core\User\Entity\UserAvatar; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; -/** - * @extends EntityRepository - */ #[Entity(name: UserAvatar::class)] -class UserAvatarRepository extends EntityRepository +class UserAvatarRepository extends AbstractRepository { - public function deleteAvatar(UserAvatar $avatar): void - { - $this->getEntityManager()->remove($avatar); - $this->getEntityManager()->flush(); - } - - public function saveAvatar(UserAvatar $avatar): UserAvatar - { - $this->getEntityManager()->persist($avatar); - $this->getEntityManager()->flush(); - - return $avatar; - } } diff --git a/src/Core/src/User/src/Repository/UserDetailRepository.php b/src/Core/src/User/src/Repository/UserDetailRepository.php index 89a58fd5..555f43b8 100644 --- a/src/Core/src/User/src/Repository/UserDetailRepository.php +++ b/src/Core/src/User/src/Repository/UserDetailRepository.php @@ -4,14 +4,11 @@ namespace Core\User\Repository; +use Core\App\Repository\AbstractRepository; use Core\User\Entity\UserDetail; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; -/** - * @extends EntityRepository - */ #[Entity(name: UserDetail::class)] -class UserDetailRepository extends EntityRepository +class UserDetailRepository extends AbstractRepository { } diff --git a/src/Core/src/User/src/Repository/UserRepository.php b/src/Core/src/User/src/Repository/UserRepository.php index 67ea88ba..f46f0f9a 100644 --- a/src/Core/src/User/src/Repository/UserRepository.php +++ b/src/Core/src/User/src/Repository/UserRepository.php @@ -5,13 +5,11 @@ namespace Core\User\Repository; use Core\Admin\Entity\Admin; -use Core\App\Exception\BadRequestException; -use Core\App\Helper\PaginationHelper; use Core\App\Message; +use Core\App\Repository\AbstractRepository; use Core\Security\Entity\OAuthClient; use Core\User\Entity\User; use Core\User\Enum\UserStatusEnum; -use Doctrine\ORM\EntityRepository; use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; use League\OAuth2\Server\Entities\ClientEntityInterface; @@ -19,82 +17,70 @@ use League\OAuth2\Server\Repositories\UserRepositoryInterface; use Mezzio\Authentication\OAuth2\Entity\UserEntity; -use function in_array; +use function array_key_exists; +use function is_string; use function password_verify; -use function sprintf; +use function strlen; -/** - * @extends EntityRepository - */ #[Entity(name: User::class)] -class UserRepository extends EntityRepository implements UserRepositoryInterface +class UserRepository extends AbstractRepository implements UserRepositoryInterface { - /** - * @throws BadRequestException - */ - public function getUsers(array $params = []): QueryBuilder + public function getUsers(array $params = [], array $filters = []): QueryBuilder { - $page = PaginationHelper::getOffsetAndLimit($params); - - $values = [ - 'user.identity', - 'user.status', - 'user.created', - 'user.updated', - ]; - - $params['order'] = $params['order'] ?? 'user.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - $params['dir'] = $params['dir'] ?? 'desc'; - if (! in_array($params['dir'], ['asc', 'desc'])) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); - } - - $qb = $this - ->getEntityManager() - ->createQueryBuilder() - ->select(['user', 'avatar', 'detail', 'roles']) + $queryBuilder = $this + ->getQueryBuilder() + ->select(['user']) ->from(User::class, 'user') - ->leftJoin('user.avatar', 'avatar') ->leftJoin('user.detail', 'detail') - ->leftJoin('user.roles', 'roles') - ->andWhere('user.status != :status') - ->setParameter('status', UserStatusEnum::Deleted) - ->orderBy($params['order'], $params['dir']) - ->setFirstResult($page['offset']) - ->setMaxResults($page['limit']); - $qb->getQuery()->useQueryCache(true); - - if (! empty($params['status'])) { - $qb->andWhere('user.status = :status')->setParameter('status', $params['status']); + ->leftJoin('user.roles', 'role') + ->andWhere('user.status != :statusNotDeleted') + ->setParameter('statusNotDeleted', UserStatusEnum::Deleted); + + if ( + array_key_exists('identity', $filters) + && is_string($filters['identity']) + && strlen($filters['identity']) > 0 + ) { + $queryBuilder + ->andWhere($queryBuilder->expr()->like('user.identity', ':identity')) + ->setParameter('identity', '%' . $filters['identity'] . '%'); } - - if (! empty($params['search'])) { - $qb->andWhere( - $qb->expr()->orX( - $qb->expr()->like('user.identity', ':search'), - $qb->expr()->like('detail.firstName', ':search'), - $qb->expr()->like('detail.lastName', ':search'), - $qb->expr()->like('detail.email', ':search') - ) - )->setParameter('search', '%' . $params['search'] . '%'); + if ( + array_key_exists('email', $filters) + && is_string($filters['email']) + && strlen($filters['email']) > 0 + ) { + $queryBuilder + ->andWhere($queryBuilder->expr()->like('detail.email', ':email')) + ->setParameter('email', '%' . $filters['email'] . '%'); } - - if (! empty($params['role'])) { - $qb->andWhere('roles.name = :role')->setParameter('role', $params['role']); + if ( + array_key_exists('status', $filters) + && is_string($filters['status']) + && strlen($filters['status']) > 0 + ) { + $queryBuilder + ->andWhere('user.status = :status') + ->setParameter('status', $filters['status']); + } + if ( + array_key_exists('role', $filters) + && is_string($filters['role']) + && strlen($filters['role']) > 0 + ) { + $queryBuilder + ->andWhere('role.name = :role') + ->setParameter('role', $filters['role']); } - return $qb; - } - - public function saveUser(User $user): User - { - $this->getEntityManager()->persist($user); - $this->getEntityManager()->flush(); + $queryBuilder + ->orderBy($params['sort'], $params['dir']) + ->setFirstResult($params['offset']) + ->setMaxResults($params['limit']) + ->groupBy('user.uuid'); + $queryBuilder->getQuery()->useQueryCache(true); - return $user; + return $queryBuilder; } /** diff --git a/src/Core/src/User/src/Repository/UserResetPasswordRepository.php b/src/Core/src/User/src/Repository/UserResetPasswordRepository.php index 7796ff1b..5c0422de 100644 --- a/src/Core/src/User/src/Repository/UserResetPasswordRepository.php +++ b/src/Core/src/User/src/Repository/UserResetPasswordRepository.php @@ -4,14 +4,11 @@ namespace Core\User\Repository; +use Core\App\Repository\AbstractRepository; use Core\User\Entity\UserResetPassword; -use Doctrine\ORM\EntityRepository; use Dot\DependencyInjection\Attribute\Entity; -/** - * @extends EntityRepository - */ #[Entity(name: UserResetPassword::class)] -class UserResetPasswordRepository extends EntityRepository +class UserResetPasswordRepository extends AbstractRepository { } diff --git a/src/Core/src/User/src/Repository/UserRoleRepository.php b/src/Core/src/User/src/Repository/UserRoleRepository.php index 4d657e75..dbdf38b5 100644 --- a/src/Core/src/User/src/Repository/UserRoleRepository.php +++ b/src/Core/src/User/src/Repository/UserRoleRepository.php @@ -4,55 +4,41 @@ namespace Core\User\Repository; -use Core\App\Exception\BadRequestException; -use Core\App\Helper\PaginationHelper; -use Core\App\Message; +use Core\App\Repository\AbstractRepository; use Core\User\Entity\UserRole; -use Doctrine\ORM\EntityRepository; -use Doctrine\ORM\Query; +use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Entity; -use function in_array; -use function sprintf; +use function array_key_exists; +use function is_string; +use function strlen; -/** - * @extends EntityRepository - */ #[Entity(name: UserRole::class)] -class UserRoleRepository extends EntityRepository +class UserRoleRepository extends AbstractRepository { - /** - * @throws BadRequestException - */ - public function getRoles(array $params = []): Query + public function getUserRoles(array $params = [], array $filters = []): QueryBuilder { - $values = [ - 'role.name', - 'role.created', - 'role.updated', - ]; - - $params['order'] = $params['order'] ?? 'role.created'; - if (! in_array($params['order'], $values)) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'order')]); - } - - $params['dir'] = $params['dir'] ?? 'desc'; - if (! in_array($params['dir'], ['asc', 'desc'])) { - throw (new BadRequestException())->setMessages([sprintf(Message::INVALID_VALUE, 'dir')]); + $queryBuilder = $this + ->getQueryBuilder() + ->select(['role']) + ->from(UserRole::class, 'role'); + + if ( + array_key_exists('name', $filters) + && is_string($filters['name']) + && strlen($filters['name']) > 0 + ) { + $queryBuilder + ->andWhere('role.name = :name') + ->setParameter('name', $filters['name']); } - $page = PaginationHelper::getOffsetAndLimit($params); + $queryBuilder + ->orderBy($params['sort'], $params['dir']) + ->setFirstResult($params['offset']) + ->setMaxResults($params['limit']); + $queryBuilder->getQuery()->useQueryCache(true); - return $this - ->getEntityManager() - ->createQueryBuilder() - ->select(['role']) - ->from(UserRole::class, 'role') - ->orderBy($params['order'], $params['dir']) - ->setFirstResult($page['offset']) - ->setMaxResults($page['limit']) - ->getQuery() - ->useQueryCache(true); + return $queryBuilder; } } diff --git a/src/Core/src/User/src/Service/UserAvatarServiceInterface.php b/src/Core/src/User/src/Service/UserAvatarServiceInterface.php deleted file mode 100644 index 7c54fa33..00000000 --- a/src/Core/src/User/src/Service/UserAvatarServiceInterface.php +++ /dev/null @@ -1,16 +0,0 @@ - AttributedServiceFactory::class, PostUserAvatarResourceHandler::class => AttributedServiceFactory::class, PostUserResourceHandler::class => AttributedServiceFactory::class, + UserAvatarService::class => AttributedServiceFactory::class, + UserResetPasswordService::class => AttributedServiceFactory::class, + UserRoleService::class => AttributedServiceFactory::class, + UserService::class => AttributedServiceFactory::class, + ], + 'aliases' => [ + UserAvatarServiceInterface::class => UserAvatarService::class, + UserResetPasswordServiceInterface::class => UserResetPasswordService::class, + UserRoleServiceInterface::class => UserRoleService::class, + UserServiceInterface::class => UserService::class, ], ]; } diff --git a/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php index 9bc1ef6e..1fca71c9 100644 --- a/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/DeleteUserAccountAvatarHandler.php @@ -5,10 +5,10 @@ namespace Api\User\Handler\Account\Avatar; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserAvatarServiceInterface; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\User\Entity\User; -use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -30,7 +30,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $user = $request->getAttribute(User::class); if (! $user->hasAvatar()) { - throw new NotFoundException(Message::AVATAR_MISSING); + throw new NotFoundException(Message::USER_AVATAR_MISSING); } $this->userAvatarService->deleteAvatar($user); diff --git a/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php index 4770776d..a749c29b 100644 --- a/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/GetUserAccountAvatarHandler.php @@ -5,10 +5,10 @@ namespace Api\User\Handler\Account\Avatar; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserAvatarServiceInterface; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\User\Entity\User; -use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -30,7 +30,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { $user = $request->getAttribute(User::class); if (! $user->hasAvatar()) { - throw new NotFoundException(Message::AVATAR_MISSING); + throw new NotFoundException(Message::USER_AVATAR_MISSING); } return $this->createResponse($request, $user->getAvatar()); diff --git a/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php b/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php index 81c55ab0..9b55aa5e 100644 --- a/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php +++ b/src/User/src/Handler/Account/Avatar/PostUserAccountAvatarHandler.php @@ -6,9 +6,9 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateAvatarInputFilter; +use Api\User\Service\UserAvatarServiceInterface; use Core\App\Exception\BadRequestException; use Core\User\Entity\User; -use Core\User\Service\UserAvatarServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -37,7 +37,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface return $this->createdResponse( $request, - $this->userAvatarService->createAvatar( + $this->userAvatarService->saveAvatar( $request->getAttribute(User::class), $this->inputFilter->getValue('avatar') ) diff --git a/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php b/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php index 1bb61e0f..da551707 100644 --- a/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/DeleteUserAccountResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\User\Handler\Account; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\User\Entity\User; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/GetUserAccountResourceHandler.php b/src/User/src/Handler/Account/GetUserAccountResourceHandler.php index 2a6eb96b..6f547bdd 100644 --- a/src/User/src/Handler/Account/GetUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/GetUserAccountResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\User\Handler\Account; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\User\Entity\User; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php b/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php index b68417bf..0685802e 100644 --- a/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php +++ b/src/User/src/Handler/Account/PatchUserAccountActivateHandler.php @@ -5,10 +5,10 @@ namespace Api\User\Handler\Account; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; diff --git a/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php b/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php index 17db99ef..dd9b39b2 100644 --- a/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/PatchUserAccountResourceHandler.php @@ -6,11 +6,11 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateUserInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\User\Entity\User; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -41,9 +41,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $request->getAttribute(User::class); - $this->userService->updateUser($user, (array) $this->inputFilter->getValues()); - - return $this->createResponse($request, $user); + return $this->createResponse( + $request, + $this->userService->saveUser( + (array) $this->inputFilter->getValues(), + $request->getAttribute(User::class) + ) + ); } } diff --git a/src/User/src/Handler/Account/PostUserAccountActivateHandler.php b/src/User/src/Handler/Account/PostUserAccountActivateHandler.php index 66f30aa1..1f521e26 100644 --- a/src/User/src/Handler/Account/PostUserAccountActivateHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountActivateHandler.php @@ -6,20 +6,18 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\ActivateAccountInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Fig\Http\Message\StatusCodeInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use function sprintf; - class PostUserAccountActivateHandler extends AbstractHandler { #[Inject( @@ -56,7 +54,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface $this->mailService->sendActivationMail($user); return $this->infoResponse( - sprintf(Message::MAIL_SENT_USER_ACTIVATION, $user->getDetail()->getEmail()), + Message::mailSentUserActivation($user->getDetail()->getEmail()), StatusCodeInterface::STATUS_CREATED ); } diff --git a/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php b/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php index 7edac54e..0fbe5265 100644 --- a/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountRecoverHandler.php @@ -6,11 +6,11 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\RecoverIdentityInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; diff --git a/src/User/src/Handler/Account/PostUserAccountResourceHandler.php b/src/User/src/Handler/Account/PostUserAccountResourceHandler.php index e973730c..29409dcf 100644 --- a/src/User/src/Handler/Account/PostUserAccountResourceHandler.php +++ b/src/User/src/Handler/Account/PostUserAccountResourceHandler.php @@ -6,11 +6,11 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\CreateUserInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -45,7 +45,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->createUser((array) $this->inputFilter->getValues()); + $user = $this->userService->saveUser((array) $this->inputFilter->getValues()); $this->mailService->sendActivationMail($user); return $this->createdResponse($request, $user); diff --git a/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php index 467a4d7b..c1a95be5 100644 --- a/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/GetUserAccountResetPasswordHandler.php @@ -5,16 +5,14 @@ namespace Api\User\Handler\Account\ResetPassword; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserResetPasswordServiceInterface; use Core\App\Exception\ExpiredException; use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Service\UserResetPasswordServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use function sprintf; - class GetUserAccountResetPasswordHandler extends AbstractHandler { #[Inject( @@ -35,12 +33,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface $userResetPassword = $this->userResetPasswordService->findOneBy(['hash' => $hash]); if (! $userResetPassword->isValid()) { - throw new ExpiredException(sprintf(Message::RESET_PASSWORD_EXPIRED, $hash)); + throw new ExpiredException(Message::RESET_PASSWORD_EXPIRED); } if ($userResetPassword->isCompleted()) { - throw new ExpiredException(sprintf(Message::RESET_PASSWORD_USED, $hash)); + throw new ExpiredException(Message::RESET_PASSWORD_USED); } - return $this->infoResponse(sprintf(Message::RESET_PASSWORD_VALID, $hash)); + return $this->infoResponse(Message::RESET_PASSWORD_VALID); } } diff --git a/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php index 0d0b022d..301226ea 100644 --- a/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/PatchUserAccountResetPasswordHandler.php @@ -6,21 +6,19 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdatePasswordInputFilter; +use Api\User\Service\UserResetPasswordServiceInterface; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\ExpiredException; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\App\Service\MailService; -use Core\User\Service\UserResetPasswordServiceInterface; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; -use function sprintf; - class PatchUserAccountResetPasswordHandler extends AbstractHandler { #[Inject( @@ -55,15 +53,15 @@ public function handle(ServerRequestInterface $request): ResponseInterface $userResetPassword = $this->userResetPasswordService->findOneBy(['hash' => $hash]); if (! $userResetPassword->isValid()) { - throw new ExpiredException(sprintf(Message::RESET_PASSWORD_EXPIRED, $hash)); + throw new ExpiredException(Message::RESET_PASSWORD_EXPIRED); } if ($userResetPassword->isCompleted()) { - throw new ConflictException(sprintf(Message::RESET_PASSWORD_USED, $hash)); + throw new ConflictException(Message::RESET_PASSWORD_USED); } - $this->userService->updateUser( - $userResetPassword->markAsCompleted()->getUser(), - (array) $this->inputFilter->getValues() + $this->userService->saveUser( + (array) $this->inputFilter->getValues(), + $userResetPassword->markAsCompleted()->getUser() ); $this->mailService->sendResetPasswordCompletedMail($userResetPassword->getUser()); diff --git a/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php b/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php index 68b47829..b7fe7a18 100644 --- a/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php +++ b/src/User/src/Handler/Account/ResetPassword/PostUserAccountResetPasswordHandler.php @@ -6,12 +6,12 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\ResetPasswordInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Fig\Http\Message\StatusCodeInterface; @@ -53,7 +53,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw new NotFoundException(Message::USER_NOT_FOUND); } - $this->userService->updateUser($user->createResetPassword()); + $this->userService->saveUser([], $user->createResetPassword()); $this->mailService->sendResetPasswordRequestedMail($user); return $this->infoResponse(Message::MAIL_SENT_RESET_PASSWORD, StatusCodeInterface::STATUS_CREATED); diff --git a/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php index 758ab30a..564eb56b 100644 --- a/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/DeleteUserAvatarResourceHandler.php @@ -5,11 +5,10 @@ namespace Api\User\Handler\User\Avatar; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserAvatarServiceInterface; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Entity\User; -use Core\User\Service\UserAvatarServiceInterface; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -31,12 +30,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); - if (! $user instanceof User) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } + $user = $this->userService->findUser($request->getAttribute('uuid')); if (! $user->hasAvatar()) { - throw new NotFoundException(Message::AVATAR_MISSING); + throw new NotFoundException(Message::USER_AVATAR_MISSING); } $this->userAvatarService->deleteAvatar($user); diff --git a/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php index 37eddbe1..5d724191 100644 --- a/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/GetUserAvatarResourceHandler.php @@ -5,10 +5,9 @@ namespace Api\User\Handler\User\Avatar; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Entity\User; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -28,12 +27,9 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); - if (! $user instanceof User) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } + $user = $this->userService->findUser($request->getAttribute('uuid')); if (! $user->hasAvatar()) { - throw new NotFoundException(Message::AVATAR_MISSING); + throw new NotFoundException(Message::USER_AVATAR_MISSING); } return $this->createResponse($request, $user->getAvatar()); diff --git a/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php b/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php index 348e2268..8d7dbb23 100644 --- a/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php +++ b/src/User/src/Handler/User/Avatar/PostUserAvatarResourceHandler.php @@ -6,12 +6,10 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateAvatarInputFilter; +use Api\User\Service\UserAvatarServiceInterface; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\NotFoundException; -use Core\App\Message; -use Core\User\Entity\User; -use Core\User\Service\UserAvatarServiceInterface; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -41,14 +39,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->getUserRepository()->find($request->getAttribute('uuid')); - if (! $user instanceof User) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } - return $this->createdResponse( $request, - $this->userAvatarService->createAvatar($user, $this->inputFilter->getValue('avatar')) + $this->userAvatarService->saveAvatar( + $this->userService->findUser($request->getAttribute('uuid')), + $this->inputFilter->getValue('avatar') + ) ); } } diff --git a/src/User/src/Handler/User/DeleteUserResourceHandler.php b/src/User/src/Handler/User/DeleteUserResourceHandler.php index c952e7d1..0d235fcc 100644 --- a/src/User/src/Handler/User/DeleteUserResourceHandler.php +++ b/src/User/src/Handler/User/DeleteUserResourceHandler.php @@ -5,9 +5,9 @@ namespace Api\User\Handler\User; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\NotFoundException; use Core\App\Exception\RuntimeException; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -29,7 +29,7 @@ public function __construct( public function handle(ServerRequestInterface $request): ResponseInterface { $this->userService->deleteUser( - $this->userService->find($request->getAttribute('uuid')) + $this->userService->findUser($request->getAttribute('uuid')) ); return $this->noContentResponse(); diff --git a/src/User/src/Handler/User/GetUserCollectionHandler.php b/src/User/src/Handler/User/GetUserCollectionHandler.php index 4a821874..21a78315 100644 --- a/src/User/src/Handler/User/GetUserCollectionHandler.php +++ b/src/User/src/Handler/User/GetUserCollectionHandler.php @@ -6,8 +6,7 @@ use Api\App\Handler\AbstractHandler; use Api\User\Collection\UserCollection; -use Core\App\Exception\BadRequestException; -use Core\User\Service\UserServiceInterface; +use Api\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -22,14 +21,11 @@ public function __construct( ) { } - /** - * @throws BadRequestException - */ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - new UserCollection($this->userService->getUserRepository()->getUsers($request->getQueryParams())) + new UserCollection($this->userService->getUsers($request->getQueryParams())) ); } } diff --git a/src/User/src/Handler/User/GetUserResourceHandler.php b/src/User/src/Handler/User/GetUserResourceHandler.php index 30bde030..9996c89d 100644 --- a/src/User/src/Handler/User/GetUserResourceHandler.php +++ b/src/User/src/Handler/User/GetUserResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\User\Handler\User; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\NotFoundException; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -28,7 +28,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - $this->userService->find($request->getAttribute('uuid')) + $this->userService->findUser($request->getAttribute('uuid')) ); } } diff --git a/src/User/src/Handler/User/PatchUserActivateHandler.php b/src/User/src/Handler/User/PatchUserActivateHandler.php index 8e10239f..a0e6d3de 100644 --- a/src/User/src/Handler/User/PatchUserActivateHandler.php +++ b/src/User/src/Handler/User/PatchUserActivateHandler.php @@ -5,11 +5,11 @@ namespace Api\User\Handler\User; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Message; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -34,7 +34,7 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->find($request->getAttribute('uuid')); + $user = $this->userService->findUser($request->getAttribute('uuid')); if ($user->isActive()) { throw new ConflictException(Message::USER_ALREADY_ACTIVATED); } diff --git a/src/User/src/Handler/User/PatchUserDeactivateHandler.php b/src/User/src/Handler/User/PatchUserDeactivateHandler.php index e309ee0b..4854f628 100644 --- a/src/User/src/Handler/User/PatchUserDeactivateHandler.php +++ b/src/User/src/Handler/User/PatchUserDeactivateHandler.php @@ -5,10 +5,10 @@ namespace Api\User\Handler\User; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Message; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -29,7 +29,7 @@ public function __construct( */ public function handle(ServerRequestInterface $request): ResponseInterface { - $user = $this->userService->find($request->getAttribute('uuid')); + $user = $this->userService->findUser($request->getAttribute('uuid')); if ($user->isPending()) { throw new ConflictException(Message::USER_ALREADY_DEACTIVATED); } diff --git a/src/User/src/Handler/User/PatchUserResourceHandler.php b/src/User/src/Handler/User/PatchUserResourceHandler.php index bd535b70..856fa599 100644 --- a/src/User/src/Handler/User/PatchUserResourceHandler.php +++ b/src/User/src/Handler/User/PatchUserResourceHandler.php @@ -6,10 +6,10 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\UpdateUserInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -38,9 +38,12 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->find($request->getAttribute('uuid')); - $this->userService->updateUser($user, (array) $this->inputFilter->getValues()); - - return $this->createResponse($request, $user); + return $this->createResponse( + $request, + $this->userService->saveUser( + (array) $this->inputFilter->getValues(), + $this->userService->findUser($request->getAttribute('uuid')) + ) + ); } } diff --git a/src/User/src/Handler/User/PostUserResourceHandler.php b/src/User/src/Handler/User/PostUserResourceHandler.php index 6f52d311..46f1e059 100644 --- a/src/User/src/Handler/User/PostUserResourceHandler.php +++ b/src/User/src/Handler/User/PostUserResourceHandler.php @@ -6,11 +6,11 @@ use Api\App\Handler\AbstractHandler; use Api\User\InputFilter\CreateUserInputFilter; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\App\Service\MailService; -use Core\User\Service\UserServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Dot\Mail\Exception\MailException; use Psr\Http\Message\ResponseInterface; @@ -43,7 +43,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface throw (new BadRequestException())->setMessages($this->inputFilter->getMessages()); } - $user = $this->userService->createUser((array) $this->inputFilter->getValues()); + $user = $this->userService->saveUser((array) $this->inputFilter->getValues()); if ($user->isPending()) { $this->mailService->sendActivationMail($user); } elseif ($user->isActive()) { diff --git a/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php b/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php index 805a8456..67657813 100644 --- a/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php +++ b/src/User/src/Handler/User/Role/GetUserRoleCollectionHandler.php @@ -6,8 +6,7 @@ use Api\App\Handler\AbstractHandler; use Api\User\Collection\UserRoleCollection; -use Core\App\Exception\BadRequestException; -use Core\User\Service\UserRoleServiceInterface; +use Api\User\Service\UserRoleServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -22,15 +21,12 @@ public function __construct( ) { } - /** - * @throws BadRequestException - */ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, new UserRoleCollection( - $this->userRoleService->getUserRoleRepository()->getRoles($request->getQueryParams()) + $this->userRoleService->getUserRoles($request->getQueryParams()) ) ); } diff --git a/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php b/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php index b9bdd27c..46297ab5 100644 --- a/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php +++ b/src/User/src/Handler/User/Role/GetUserRoleResourceHandler.php @@ -5,8 +5,8 @@ namespace Api\User\Handler\User\Role; use Api\App\Handler\AbstractHandler; +use Api\User\Service\UserRoleServiceInterface; use Core\App\Exception\NotFoundException; -use Core\User\Service\UserRoleServiceInterface; use Dot\DependencyInjection\Attribute\Inject; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; @@ -28,7 +28,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface { return $this->createResponse( $request, - $this->userRoleService->find($request->getAttribute('uuid')) + $this->userRoleService->findUserRole($request->getAttribute('uuid')) ); } } diff --git a/src/User/src/InputFilter/ActivateAccountInputFilter.php b/src/User/src/InputFilter/ActivateAccountInputFilter.php index 31ff9cc1..80fe8895 100644 --- a/src/User/src/InputFilter/ActivateAccountInputFilter.php +++ b/src/User/src/InputFilter/ActivateAccountInputFilter.php @@ -4,13 +4,10 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\EmailInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\EmailInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class ActivateAccountInputFilter extends InputFilter +class ActivateAccountInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/CreateUserDetailInputFilter.php b/src/User/src/InputFilter/CreateUserDetailInputFilter.php index 9441cc8f..86d830d0 100644 --- a/src/User/src/InputFilter/CreateUserDetailInputFilter.php +++ b/src/User/src/InputFilter/CreateUserDetailInputFilter.php @@ -4,15 +4,12 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\EmailInput; -use Api\User\InputFilter\Input\FirstNameInput; -use Api\User\InputFilter\Input\LastNameInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\EmailInput; +use Api\App\InputFilter\Input\FirstNameInput; +use Api\App\InputFilter\Input\LastNameInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class CreateUserDetailInputFilter extends InputFilter +class CreateUserDetailInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/CreateUserInputFilter.php b/src/User/src/InputFilter/CreateUserInputFilter.php index d29c0cfb..cabde723 100644 --- a/src/User/src/InputFilter/CreateUserInputFilter.php +++ b/src/User/src/InputFilter/CreateUserInputFilter.php @@ -4,17 +4,14 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\IdentityInput; -use Api\User\InputFilter\Input\PasswordConfirmInput; -use Api\User\InputFilter\Input\PasswordInput; +use Api\App\InputFilter\Input\IdentityInput; +use Api\App\InputFilter\Input\PasswordConfirmInput; +use Api\App\InputFilter\Input\PasswordInput; use Api\User\InputFilter\Input\StatusInput; +use Core\App\InputFilter\AbstractInputFilter; use Laminas\InputFilter\CollectionInputFilter; -use Laminas\InputFilter\InputFilter; -/** - * @extends InputFilter - */ -class CreateUserInputFilter extends InputFilter +class CreateUserInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/Input/FirstNameInput.php b/src/User/src/InputFilter/Input/FirstNameInput.php deleted file mode 100644 index 0670b78b..00000000 --- a/src/User/src/InputFilter/Input/FirstNameInput.php +++ /dev/null @@ -1,23 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - } -} diff --git a/src/User/src/InputFilter/Input/LastNameInput.php b/src/User/src/InputFilter/Input/LastNameInput.php deleted file mode 100644 index 1ccfbe38..00000000 --- a/src/User/src/InputFilter/Input/LastNameInput.php +++ /dev/null @@ -1,23 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - } -} diff --git a/src/User/src/InputFilter/Input/StatusInput.php b/src/User/src/InputFilter/Input/StatusInput.php index afbb705e..d6586e72 100644 --- a/src/User/src/InputFilter/Input/StatusInput.php +++ b/src/User/src/InputFilter/Input/StatusInput.php @@ -11,8 +11,6 @@ use Laminas\InputFilter\Input; use Laminas\Validator\InArray; -use function sprintf; - class StatusInput extends Input { public function __construct(?string $name = null, bool $isRequired = true) @@ -29,7 +27,7 @@ public function __construct(?string $name = null, bool $isRequired = true) $this->getValidatorChain() ->attachByName(InArray::class, [ 'haystack' => UserStatusEnum::cases(), - 'message' => sprintf(Message::INVALID_VALUE, 'status'), + 'message' => Message::invalidValue('status'), ], true); } } diff --git a/src/User/src/InputFilter/Input/UuidInput.php b/src/User/src/InputFilter/Input/UuidInput.php deleted file mode 100644 index a520550b..00000000 --- a/src/User/src/InputFilter/Input/UuidInput.php +++ /dev/null @@ -1,23 +0,0 @@ -setRequired($isRequired); - - $this->getFilterChain() - ->attachByName(StringTrim::class) - ->attachByName(StripTags::class); - } -} diff --git a/src/User/src/InputFilter/RecoverIdentityInputFilter.php b/src/User/src/InputFilter/RecoverIdentityInputFilter.php index a22f1615..d5862914 100644 --- a/src/User/src/InputFilter/RecoverIdentityInputFilter.php +++ b/src/User/src/InputFilter/RecoverIdentityInputFilter.php @@ -4,13 +4,10 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\EmailInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\EmailInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class RecoverIdentityInputFilter extends InputFilter +class RecoverIdentityInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/ResetPasswordInputFilter.php b/src/User/src/InputFilter/ResetPasswordInputFilter.php index b5024a04..e32493c8 100644 --- a/src/User/src/InputFilter/ResetPasswordInputFilter.php +++ b/src/User/src/InputFilter/ResetPasswordInputFilter.php @@ -4,14 +4,11 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\EmailInput; -use Api\User\InputFilter\Input\IdentityInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\EmailInput; +use Api\App\InputFilter\Input\IdentityInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class ResetPasswordInputFilter extends InputFilter +class ResetPasswordInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/UpdateAvatarInputFilter.php b/src/User/src/InputFilter/UpdateAvatarInputFilter.php index 22deb0b0..303e20b8 100644 --- a/src/User/src/InputFilter/UpdateAvatarInputFilter.php +++ b/src/User/src/InputFilter/UpdateAvatarInputFilter.php @@ -4,16 +4,13 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\AvatarInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\ImageInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class UpdateAvatarInputFilter extends InputFilter +class UpdateAvatarInputFilter extends AbstractInputFilter { public function __construct() { - $this->add(new AvatarInput('avatar')); + $this->add(new ImageInput('avatar')); } } diff --git a/src/User/src/InputFilter/UpdatePasswordInputFilter.php b/src/User/src/InputFilter/UpdatePasswordInputFilter.php index 5a3161b7..ef797ebc 100644 --- a/src/User/src/InputFilter/UpdatePasswordInputFilter.php +++ b/src/User/src/InputFilter/UpdatePasswordInputFilter.php @@ -4,14 +4,11 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\PasswordConfirmInput; -use Api\User\InputFilter\Input\PasswordInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\PasswordConfirmInput; +use Api\App\InputFilter\Input\PasswordInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class UpdatePasswordInputFilter extends InputFilter +class UpdatePasswordInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/UpdateUserDetailInputFilter.php b/src/User/src/InputFilter/UpdateUserDetailInputFilter.php index bc46b9e6..e80a6435 100644 --- a/src/User/src/InputFilter/UpdateUserDetailInputFilter.php +++ b/src/User/src/InputFilter/UpdateUserDetailInputFilter.php @@ -4,15 +4,12 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\EmailInput; -use Api\User\InputFilter\Input\FirstNameInput; -use Api\User\InputFilter\Input\LastNameInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\EmailInput; +use Api\App\InputFilter\Input\FirstNameInput; +use Api\App\InputFilter\Input\LastNameInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class UpdateUserDetailInputFilter extends InputFilter +class UpdateUserDetailInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/UpdateUserInputFilter.php b/src/User/src/InputFilter/UpdateUserInputFilter.php index 842ffef4..d112bdea 100644 --- a/src/User/src/InputFilter/UpdateUserInputFilter.php +++ b/src/User/src/InputFilter/UpdateUserInputFilter.php @@ -4,17 +4,14 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\IdentityInput; -use Api\User\InputFilter\Input\PasswordConfirmInput; -use Api\User\InputFilter\Input\PasswordInput; +use Api\App\InputFilter\Input\IdentityInput; +use Api\App\InputFilter\Input\PasswordConfirmInput; +use Api\App\InputFilter\Input\PasswordInput; use Api\User\InputFilter\Input\StatusInput; +use Core\App\InputFilter\AbstractInputFilter; use Laminas\InputFilter\CollectionInputFilter; -use Laminas\InputFilter\InputFilter; -/** - * @extends InputFilter - */ -class UpdateUserInputFilter extends InputFilter +class UpdateUserInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/InputFilter/UserRoleInputFilter.php b/src/User/src/InputFilter/UserRoleInputFilter.php index df268ff9..040c8eef 100644 --- a/src/User/src/InputFilter/UserRoleInputFilter.php +++ b/src/User/src/InputFilter/UserRoleInputFilter.php @@ -4,13 +4,10 @@ namespace Api\User\InputFilter; -use Api\User\InputFilter\Input\UuidInput; -use Laminas\InputFilter\InputFilter; +use Api\App\InputFilter\Input\UuidInput; +use Core\App\InputFilter\AbstractInputFilter; -/** - * @extends InputFilter - */ -class UserRoleInputFilter extends InputFilter +class UserRoleInputFilter extends AbstractInputFilter { public function __construct() { diff --git a/src/User/src/OpenAPI.php b/src/User/src/OpenAPI.php index 13c0f3db..3a8c9bb3 100644 --- a/src/User/src/OpenAPI.php +++ b/src/User/src/OpenAPI.php @@ -36,6 +36,7 @@ use Core\User\Entity\UserResetPassword; use Core\User\Entity\UserRole; use Core\User\Enum\UserResetPasswordStatusEnum; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use DateTimeImmutable; use Fig\Http\Message\StatusCodeInterface; @@ -1252,7 +1253,7 @@ schema: 'UserRole', properties: [ new OA\Property(property: 'uuid', type: 'string', example: '1234abcd-abcd-4321-12ab-123456abcdef'), - new OA\Property(property: 'name', type: 'string', example: UserRole::ROLE_USER), + new OA\Property(property: 'name', type: 'string', example: UserRoleEnum::User->value), new OA\Property( property: '_links', properties: [ diff --git a/src/User/src/RoutesDelegator.php b/src/User/src/RoutesDelegator.php index 60a876c8..69ea11c5 100644 --- a/src/User/src/RoutesDelegator.php +++ b/src/User/src/RoutesDelegator.php @@ -29,6 +29,7 @@ use Api\User\Handler\User\PostUserResourceHandler; use Api\User\Handler\User\Role\GetUserRoleCollectionHandler; use Api\User\Handler\User\Role\GetUserRoleResourceHandler; +use Core\App\ConfigProvider; use Dot\Router\RouteCollectorInterface; use Mezzio\Application; use Psr\Container\ContainerExceptionInterface; @@ -43,7 +44,7 @@ class RoutesDelegator */ public function __invoke(ContainerInterface $container, string $serviceName, callable $callback): Application { - $uuid = \Api\App\RoutesDelegator::REGEXP_UUID; + $uuid = ConfigProvider::REGEXP_UUID; /** @var RouteCollectorInterface $routeCollector */ $routeCollector = $container->get(RouteCollectorInterface::class); diff --git a/src/Core/src/User/src/Service/UserAvatarService.php b/src/User/src/Service/UserAvatarService.php similarity index 84% rename from src/Core/src/User/src/Service/UserAvatarService.php rename to src/User/src/Service/UserAvatarService.php index 768fa157..e8485644 100644 --- a/src/Core/src/User/src/Service/UserAvatarService.php +++ b/src/User/src/Service/UserAvatarService.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace Core\User\Service; +namespace Api\User\Service; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; use Core\User\Repository\UserAvatarRepository; use Dot\DependencyInjection\Attribute\Inject; +use FilesystemIterator; use Laminas\Diactoros\UploadedFile; use Psr\Http\Message\UploadedFileInterface; use Ramsey\Uuid\Uuid; @@ -16,6 +17,7 @@ use function file_exists; use function is_readable; use function mkdir; +use function rmdir; use function rtrim; use function sprintf; use function unlink; @@ -43,7 +45,28 @@ public function getUserAvatarRepository(): UserAvatarRepository return $this->userAvatarRepository; } - public function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar + public function deleteAvatar(User $user): void + { + if (! $user->hasAvatar()) { + return; + } + + $avatar = $user->getAvatar(); + assert($avatar instanceof UserAvatar); + + $path = $this->getUserAvatarDirectoryPath($user); + $this->userAvatarRepository->deleteResource($avatar); + $this->deleteAvatarFile($path . $avatar->getName()); + + if (file_exists($path)) { + $fsIterator = new FilesystemIterator($path, FilesystemIterator::SKIP_DOTS); + if (! $fsIterator->valid()) { + rmdir($path); + } + } + } + + public function saveAvatar(User $user, UploadedFile $uploadedFile): UserAvatar { $path = $this->getUserAvatarDirectoryPath($user); @@ -59,31 +82,17 @@ public function createAvatar(User $user, UploadedFile $uploadedFile): UserAvatar $fileName = $this->createFileName((string) $uploadedFile->getClientMediaType()); $this->saveAvatarImage($uploadedFile, $path . $fileName); - $this->userAvatarRepository->saveAvatar($avatar->setName($fileName)); + $this->userAvatarRepository->saveResource($avatar->setName($fileName)); return $avatar; } - public function deleteAvatar(User $user): void - { - if (! $user->hasAvatar()) { - return; - } - - $avatar = $user->getAvatar(); - assert($avatar instanceof UserAvatar); - - $path = $this->getUserAvatarDirectoryPath($user); - $this->userAvatarRepository->deleteAvatar($avatar); - $this->deleteAvatarFile($path . $avatar->getName()); - } - - protected function getUserAvatarDirectoryPath(User $user): string + protected function createFileName(string $fileType): string { return sprintf( - '%s/%s/', - rtrim($this->config['uploads']['user']['path'], '/'), - $user->getUuid()->toString() + 'avatar-%s.%s', + Uuid::uuid4()->toString(), + self::EXTENSIONS[$fileType] ); } @@ -101,12 +110,12 @@ protected function ensureDirectoryExists(string $path): void } } - protected function createFileName(string $fileType): string + protected function getUserAvatarDirectoryPath(User $user): string { return sprintf( - 'avatar-%s.%s', - Uuid::uuid4()->toString(), - self::EXTENSIONS[$fileType] + '%s/%s/', + rtrim($this->config['uploads']['user']['path'], '/'), + $user->getUuid()->toString() ); } diff --git a/src/User/src/Service/UserAvatarServiceInterface.php b/src/User/src/Service/UserAvatarServiceInterface.php new file mode 100644 index 00000000..0be7010b --- /dev/null +++ b/src/User/src/Service/UserAvatarServiceInterface.php @@ -0,0 +1,19 @@ +userRoleRepository->find($id); if (! $userRole instanceof UserRole) { @@ -37,4 +41,24 @@ public function find(string $id): UserRole return $userRole; } + + /** + * @param array $params + */ + public function getUserRoles(array $params): QueryBuilder + { + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($params, 'role.created'); + + $sortableColumns = [ + 'role.name', + 'role.created', + 'role.updated', + ]; + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'role.created'; + } + + return $this->userRoleRepository->getUserRoles($params, $filters); + } } diff --git a/src/Core/src/User/src/Service/UserRoleServiceInterface.php b/src/User/src/Service/UserRoleServiceInterface.php similarity index 56% rename from src/Core/src/User/src/Service/UserRoleServiceInterface.php rename to src/User/src/Service/UserRoleServiceInterface.php index b6fb8c76..84fd129e 100644 --- a/src/Core/src/User/src/Service/UserRoleServiceInterface.php +++ b/src/User/src/Service/UserRoleServiceInterface.php @@ -2,11 +2,12 @@ declare(strict_types=1); -namespace Core\User\Service; +namespace Api\User\Service; use Core\App\Exception\NotFoundException; use Core\User\Entity\UserRole; use Core\User\Repository\UserRoleRepository; +use Doctrine\ORM\QueryBuilder; interface UserRoleServiceInterface { @@ -15,5 +16,10 @@ public function getUserRoleRepository(): UserRoleRepository; /** * @throws NotFoundException */ - public function find(string $id): UserRole; + public function findUserRole(string $id): UserRole; + + /** + * @param array $params + */ + public function getUserRoles(array $params): QueryBuilder; } diff --git a/src/Core/src/User/src/Service/UserService.php b/src/User/src/Service/UserService.php similarity index 64% rename from src/Core/src/User/src/Service/UserService.php rename to src/User/src/Service/UserService.php index 74847e63..048ba68e 100644 --- a/src/Core/src/User/src/Service/UserService.php +++ b/src/User/src/Service/UserService.php @@ -2,26 +2,31 @@ declare(strict_types=1); -namespace Core\User\Service; +namespace Api\User\Service; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; +use Core\App\Helper\Paginator; use Core\App\Message; use Core\Security\Repository\OAuthAccessTokenRepository; use Core\Security\Repository\OAuthRefreshTokenRepository; use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use Core\User\Repository\UserDetailRepository; use Core\User\Repository\UserRepository; use Core\User\Repository\UserRoleRepository; +use Doctrine\ORM\QueryBuilder; use Dot\DependencyInjection\Attribute\Inject; use Ramsey\Uuid\UuidInterface; +use function array_key_exists; use function count; use function date; +use function in_array; use function is_array; class UserService implements UserServiceInterface @@ -51,45 +56,16 @@ public function getUserRepository(): UserRepository public function activateUser(User $user): User { - return $this->userRepository->saveUser($user->activate()); - } + $this->userRepository->saveResource($user->activate()); - public function deactivateUser(User $user): User - { - return $this->userRepository->saveUser($user->deactivate()); + return $user; } - /** - * @throws ConflictException - * @throws NotFoundException - */ - public function createUser(array $data = []): User + public function deactivateUser(User $user): User { - $detail = (new UserDetail()) - ->setFirstName($data['detail']['firstName'] ?? null) - ->setLastName($data['detail']['lastName'] ?? null) - ->setEmail($data['detail']['email']); - - $user = (new User()) - ->setDetail($detail) - ->setIdentity($data['identity']) - ->usePassword($data['password']) - ->setStatus($data['status'] ?? UserStatusEnum::Pending); - $detail->setUser($user); - - $this->validateUniqueUser($user->getIdentity(), $user->getDetail()->getEmail()); + $this->userRepository->saveResource($user->deactivate()); - if (isset($data['roles']) && is_array($data['roles']) && count($data['roles']) > 0) { - foreach ($data['roles'] as $roleData) { - $userRole = $this->userRoleRepository->find($roleData['uuid']); - if (! $userRole instanceof UserRole) { - throw new NotFoundException(Message::ROLE_NOT_FOUND); - } - $user->addRole($userRole); - } - } - - return $this->userRepository->saveUser($user); + return $user; } public function deleteUser(User $user): User @@ -99,74 +75,6 @@ public function deleteUser(User $user): User return $this->anonymizeUser($user); } - private function anonymizeUser(User $user): User - { - $placeholder = $this->getAnonymousPlaceholder(); - - $user - ->setStatus(UserStatusEnum::Deleted) - ->setIdentity($placeholder . $this->config['userAnonymizeAppend']) - ->getDetail() - ->setFirstName($placeholder) - ->setLastName($placeholder) - ->setEmail($placeholder); - - return $this->userRepository->saveUser($user); - } - - private function getAnonymousPlaceholder(): string - { - return 'anonymous' . date('dmYHis'); - } - - /** - * @throws ConflictException - */ - private function validateUniqueUser(string $identity, string $email, ?UuidInterface $uuid = null): void - { - $user = $this->userRepository->findOneBy(['identity' => $identity]); - if ($user instanceof User) { - if ($uuid === null) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - if ($user->getUuid()->toString() !== $uuid->toString()) { - throw new ConflictException(Message::DUPLICATE_IDENTITY); - } - } - - $userDetail = $this->userDetailRepository->findOneBy(['email' => $email]); - if ($userDetail instanceof UserDetail) { - if ($uuid === null) { - throw new ConflictException(Message::DUPLICATE_EMAIL); - } - if ($userDetail->getUser()->getUuid()->toString() !== $uuid->toString()) { - throw new ConflictException(Message::DUPLICATE_EMAIL); - } - } - } - - private function revokeTokens(User $user): void - { - $accessTokens = $this->oAuthAccessTokenRepository->findAccessTokens($user->getIdentity()); - foreach ($accessTokens as $accessToken) { - $this->oAuthAccessTokenRepository->revokeAccessToken($accessToken->getToken()); - $this->oAuthRefreshTokenRepository->revokeRefreshToken($accessToken->getToken()); - } - } - - /** - * @throws NotFoundException - */ - public function find(string $id): User - { - $user = $this->userRepository->find($id); - if (! $user instanceof User || $user->isDeleted()) { - throw new NotFoundException(Message::USER_NOT_FOUND); - } - - return $user; - } - /** * @throws NotFoundException */ @@ -207,43 +115,91 @@ public function findOneBy(array $params): User } /** - * @throws BadRequestException - * @throws ConflictException * @throws NotFoundException */ - public function updateUser(User $user, array $data = []): User + public function findUser(string $id): User { - if (isset($data['identity'])) { - $user->setIdentity($data['identity']); + $user = $this->userRepository->find($id); + if (! $user instanceof User || $user->isDeleted()) { + throw new NotFoundException(Message::USER_NOT_FOUND); } - if (isset($data['password'])) { - $user->usePassword($data['password']); - } + return $user; + } - if (isset($data['status'])) { - $user->setStatus($data['status']); + /** + * @param array $params + */ + public function getUsers(array $params): QueryBuilder + { + $filters = $params['filters'] ?? []; + $params = Paginator::getParams($params, 'user.created'); + + $sortableColumns = [ + 'user.identity', + 'user.status', + 'user.created', + 'user.updated', + 'detail.firstName', + 'detail.lastName', + 'detail.email', + 'role.name', + ]; + if (! in_array($params['sort'], $sortableColumns, true)) { + $params['sort'] = 'user.created'; } - if (isset($data['hash'])) { - $user->setHash($data['hash']); - } + return $this->userRepository->getUsers($params, $filters); + } - if (isset($data['detail']['firstName'])) { - $user->getDetail()->setFirstname($data['detail']['firstName']); + /** + * @throws BadRequestException + * @throws ConflictException + * @throws NotFoundException + */ + public function saveUser(array $data, ?User $user = null): User + { + if (! $user instanceof User) { + $user = new User(); } - if (isset($data['detail']['lastName'])) { - $user->getDetail()->setLastName($data['detail']['lastName']); + if (array_key_exists('identity', $data) && $data['identity'] !== null && ! $user->hasIdentity()) { + $user->setIdentity($data['identity']); } - - if (isset($data['detail']['email'])) { - $user->getDetail()->setEmail($data['detail']['email']); + if (array_key_exists('password', $data) && $data['password'] !== null) { + $user->usePassword($data['password']); + } + if (array_key_exists('hash', $data) && $data['hash'] !== null) { + $user->setHash($data['hash']); + } + if (array_key_exists('status', $data) && $data['status'] !== null) { + $status = $data['status']; + if (! $status instanceof UserStatusEnum) { + $status = UserStatusEnum::tryFrom($status); + } + if (! $status instanceof UserStatusEnum) { + throw new BadRequestException(Message::invalidValue('status')); + } + $user->setStatus($status); + } + if (array_key_exists('detail', $data) && is_array($data['detail'])) { + if (! $user->hasDetail()) { + $user->setDetail((new UserDetail())->setUser($user)); + } + if (array_key_exists('firstName', $data['detail']) && $data['detail']['firstName'] !== null) { + $user->getDetail()->setFirstname($data['detail']['firstName']); + } + if (array_key_exists('lastName', $data['detail']) && $data['detail']['lastName'] !== null) { + $user->getDetail()->setLastName($data['detail']['lastName']); + } + if (array_key_exists('email', $data['detail']) && $data['detail']['email'] !== null) { + $user->getDetail()->setEmail($data['detail']['email']); + } } $this->validateUniqueUser($user->getIdentity(), $user->getDetail()->getEmail(), $user->getUuid()); - if (isset($data['roles']) && is_array($data['roles']) && count($data['roles']) > 0) { + if (array_key_exists('roles', $data) && count($data['roles']) > 0) { $user->resetRoles(); foreach ($data['roles'] as $roleData) { $userRole = $this->userRoleRepository->find($roleData['uuid']); @@ -255,9 +211,70 @@ public function updateUser(User $user, array $data = []): User } if (! $user->hasRoles()) { - throw (new BadRequestException())->setMessages([Message::RESTRICTION_ROLES]); + $user->addRole( + $this->userRoleRepository->findOneBy(['name' => UserRoleEnum::User]) + ); } - return $this->userRepository->saveUser($user); + $this->userRepository->saveResource($user); + + return $user; + } + + private function anonymizeUser(User $user): User + { + $placeholder = $this->getAnonymousPlaceholder(); + + $user + ->setStatus(UserStatusEnum::Deleted) + ->setIdentity($placeholder . $this->config['userAnonymizeAppend']) + ->getDetail() + ->setFirstName($placeholder) + ->setLastName($placeholder) + ->setEmail($placeholder); + + $this->userRepository->saveResource($user); + + return $user; + } + + private function getAnonymousPlaceholder(): string + { + return 'anonymous' . date('dmYHis'); + } + + private function revokeTokens(User $user): void + { + $accessTokens = $this->oAuthAccessTokenRepository->findAccessTokens($user->getIdentity()); + foreach ($accessTokens as $accessToken) { + $this->oAuthAccessTokenRepository->revokeAccessToken($accessToken->getToken()); + $this->oAuthRefreshTokenRepository->revokeRefreshToken($accessToken->getToken()); + } + } + + /** + * @throws ConflictException + */ + private function validateUniqueUser(string $identity, string $email, ?UuidInterface $uuid = null): void + { + $user = $this->userRepository->findOneBy(['identity' => $identity]); + if ($user instanceof User) { + if ($uuid === null) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + if ($user->getUuid()->toString() !== $uuid->toString()) { + throw new ConflictException(Message::DUPLICATE_IDENTITY); + } + } + + $userDetail = $this->userDetailRepository->findOneBy(['email' => $email]); + if ($userDetail instanceof UserDetail) { + if ($uuid === null) { + throw new ConflictException(Message::DUPLICATE_EMAIL); + } + if ($userDetail->getUser()->getUuid()->toString() !== $uuid->toString()) { + throw new ConflictException(Message::DUPLICATE_EMAIL); + } + } } } diff --git a/src/Core/src/User/src/Service/UserServiceInterface.php b/src/User/src/Service/UserServiceInterface.php similarity index 77% rename from src/Core/src/User/src/Service/UserServiceInterface.php rename to src/User/src/Service/UserServiceInterface.php index affc2f54..4373f8e1 100644 --- a/src/Core/src/User/src/Service/UserServiceInterface.php +++ b/src/User/src/Service/UserServiceInterface.php @@ -2,13 +2,14 @@ declare(strict_types=1); -namespace Core\User\Service; +namespace Api\User\Service; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; use Core\User\Entity\User; use Core\User\Repository\UserRepository; +use Doctrine\ORM\QueryBuilder; interface UserServiceInterface { @@ -16,21 +17,10 @@ public function getUserRepository(): UserRepository; public function activateUser(User $user): User; - /** - * @throws ConflictException - * @throws NotFoundException - */ - public function createUser(array $data = []): User; - public function deactivateUser(User $user): User; public function deleteUser(User $user): User; - /** - * @throws NotFoundException - */ - public function find(string $id): User; - /** * @throws NotFoundException */ @@ -43,10 +33,20 @@ public function findByIdentity(string $identity): ?User; */ public function findOneBy(array $params): User; + /** + * @throws NotFoundException + */ + public function findUser(string $id): User; + + /** + * @param array $params + */ + public function getUsers(array $params): QueryBuilder; + /** * @throws BadRequestException * @throws ConflictException * @throws NotFoundException */ - public function updateUser(User $user, array $data = []): User; + public function saveUser(array $data, ?User $user = null): User; } diff --git a/test/Functional/AbstractFunctionalTest.php b/test/Functional/AbstractFunctionalTest.php index 389f6c06..4cd41d72 100644 --- a/test/Functional/AbstractFunctionalTest.php +++ b/test/Functional/AbstractFunctionalTest.php @@ -8,16 +8,19 @@ use ApiTest\Functional\Traits\DatabaseTrait; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Core\Admin\Enum\AdminStatusEnum; use Core\App\Entity\RoleInterface; use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use Doctrine\ORM\EntityManagerInterface; use Fig\Http\Message\RequestMethodInterface; use Fig\Http\Message\StatusCodeInterface; use Laminas\Diactoros\ServerRequest; +use Laminas\ServiceManager\ServiceManager; use Mezzio\Application; use Mezzio\MiddlewareFactory; use PHPUnit\Framework\TestCase; @@ -358,9 +361,11 @@ protected function assertBetween(int $value, int $from, int $to): void protected function replaceService(string $service, object $mockInstance): void { - $this->getContainer()->setAllowOverride(true); - $this->getContainer()->setService($service, $mockInstance); - $this->getContainer()->setAllowOverride(false); + /** @var ServiceManager $container */ + $container = $this->getContainer(); + $container->setAllowOverride(true); + $container->setService($service, $mockInstance); + $container->setAllowOverride(false); } /** @@ -369,7 +374,7 @@ protected function replaceService(string $service, object $mockInstance): void */ protected function getValidUserData(array $data = []): array { - $userRole = $this->findUserRole(UserRole::ROLE_USER); + $userRole = $this->findUserRole(UserRoleEnum::User); $this->assertInstanceOf(UserRole::class, $userRole); return [ @@ -509,7 +514,7 @@ protected function createAdmin(): Admin $adminRoleRepository = $this->getEntityManager()->getRepository(AdminRole::class); /** @var RoleInterface $adminRole */ - $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRoleEnum::Admin]); $data = $this->getValidAdminData(); @@ -533,7 +538,7 @@ protected function createAdmin(): Admin */ protected function createUser(array $data = []): User { - $userRole = $this->findUserRole(UserRole::ROLE_USER); + $userRole = $this->findUserRole(UserRoleEnum::User); $this->assertInstanceOf(UserRole::class, $userRole); $userData = $this->getValidUserData(); @@ -562,10 +567,10 @@ protected function createUser(array $data = []): User * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ - public function findUserRole(string $name): ?UserRole + public function findUserRole(UserRoleEnum $role): ?UserRole { $userRoleRepository = $this->getEntityManager()->getRepository(UserRole::class); - return $userRoleRepository->findOneBy(['name' => $name]); + return $userRoleRepository->findOneBy(['name' => $role]); } } diff --git a/test/Functional/AdminTest.php b/test/Functional/AdminTest.php index 5df30489..5bac840b 100644 --- a/test/Functional/AdminTest.php +++ b/test/Functional/AdminTest.php @@ -7,11 +7,13 @@ use BackedEnum; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Core\App\Message; use Core\App\Service\MailService; use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use PHPUnit\Framework\MockObject\Exception; use Psr\Container\ContainerExceptionInterface; @@ -138,7 +140,7 @@ public function testCannotCreateDuplicateAdminAccount(): void $adminRoleRepository = $this->getEntityManager()->getRepository(AdminRole::class); /** @var AdminRole $adminRole */ - $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRoleEnum::Admin]); $requestBody = [ 'identity' => $admin->getIdentity(), @@ -146,6 +148,7 @@ public function testCannotCreateDuplicateAdminAccount(): void 'passwordConfirm' => self::DEFAULT_PASSWORD, 'firstName' => $admin->getFirstName(), 'lastName' => $admin->getLastName(), + 'status' => $admin->getStatus()->value, 'roles' => [ [ 'uuid' => $adminRole->getUuid()->toString(), @@ -174,7 +177,7 @@ public function testAdminCanCreateAdminAccount(): void $adminRoleRepository = $this->getEntityManager()->getRepository(AdminRole::class); $adminRepository = $this->getEntityManager()->getRepository(Admin::class); - $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRole::ROLE_ADMIN]); + $adminRole = $adminRoleRepository->findOneBy(['name' => AdminRoleEnum::Admin]); $this->assertInstanceOf(AdminRole::class, $adminRole); $requestBody = [ @@ -183,6 +186,7 @@ public function testAdminCanCreateAdminAccount(): void 'passwordConfirm' => self::DEFAULT_PASSWORD, 'firstName' => 'Admin', 'lastName' => 'Test', + 'status' => $admin->getStatus()->value, 'roles' => [ [ 'uuid' => $adminRole->getUuid()->toString(), @@ -200,9 +204,9 @@ public function testAdminCanCreateAdminAccount(): void $this->assertSame($requestBody['firstName'], $newAdmin->getFirstName()); $this->assertSame($requestBody['lastName'], $newAdmin->getLastName()); - $newAdmin->getRoles()->map(function ($role) use ($adminRole) { + foreach ($newAdmin->getRoles() as $role) { $this->assertSame($adminRole, $role); - }); + } } /** @@ -310,17 +314,14 @@ public function testAdminCanViewAdminRole(): void $admin = $this->createAdmin(); $this->loginAs($admin->getIdentity(), self::DEFAULT_PASSWORD, 'admin', 'admin'); - $adminRole = new AdminRole(); - $adminRole->setName('new_admin_role'); - $this->getEntityManager()->persist($adminRole); - $this->getEntityManager()->flush(); + $adminRole = $admin->getRoles()[0]; $response = $this->get('/admin/role/' . $adminRole->getUuid()->toString()); $data = json_decode($response->getBody()->getContents(), true); $this->assertResponseOk($response); $this->assertSame($adminRole->getUuid()->toString(), $data['uuid']); - $this->assertSame($adminRole->getName(), $data['name']); + $this->assertSame($adminRole->getName()->value, $data['name']); } /** @@ -334,7 +335,7 @@ public function testAdminCreateUserAccountDuplicateEmail(): void $this->loginAs($admin->getIdentity(), self::DEFAULT_PASSWORD, 'admin', 'admin'); - $userRole = $this->findUserRole(UserRole::ROLE_USER); + $userRole = $this->findUserRole(UserRoleEnum::User); $this->assertInstanceOf(UserRole::class, $userRole); $userData = [ @@ -372,7 +373,7 @@ public function testAdminCanCreateUserAccount(): void $this->loginAs($admin->getIdentity(), self::DEFAULT_PASSWORD, 'admin', 'admin'); $userRoleRepository = $this->getEntityManager()->getRepository(UserRole::class); - $userRole = $userRoleRepository->findOneBy(['name' => UserRole::ROLE_USER]); + $userRole = $userRoleRepository->findOneBy(['name' => UserRoleEnum::User]); $this->assertInstanceOf(UserRole::class, $userRole); $mailService = $this->createMock(MailService::class); @@ -409,7 +410,6 @@ public function testAdminCanCreateUserAccount(): void $this->assertSame($userData['identity'], $data['identity']); $this->assertSame(UserStatusEnum::Pending->value, $data['status']); $this->assertEmpty($data['avatar']); - $this->assertEmpty($data['resetPasswords']); $this->assertArrayHasKey('firstName', $data['detail']); $this->assertArrayHasKey('lastName', $data['detail']); $this->assertArrayHasKey('email', $data['detail']); @@ -418,7 +418,7 @@ public function testAdminCanCreateUserAccount(): void $this->assertSame($userData['detail']['email'], $data['detail']['email']); $this->assertNotEmpty($data['roles']); $this->assertSame($userRole->getUuid()->toString(), $data['roles'][0]['uuid']); - $this->assertSame($userRole->getName(), $data['roles'][0]['name']); + $this->assertSame($userRole->getName()->value, $data['roles'][0]['name']); } /** @@ -514,9 +514,7 @@ public function testAdminUpdateUserAccountDuplicateEmail(): void */ public function testAdminCanUpdateUserAccount(): void { - $userRole = (new UserRole())->setName('new_role'); - $this->getEntityManager()->persist($userRole); - $this->getEntityManager()->flush(); + $userRole = $this->findUserRole(UserRoleEnum::User); $user = $this->createUser(); $admin = $this->createAdmin(); diff --git a/test/Functional/UserTest.php b/test/Functional/UserTest.php index 52aab6c3..a9bd0dcc 100644 --- a/test/Functional/UserTest.php +++ b/test/Functional/UserTest.php @@ -4,6 +4,8 @@ namespace ApiTest\Functional; +use Api\User\Service\UserAvatarService; +use Api\User\Service\UserAvatarServiceInterface; use Core\App\Message; use Core\App\Service\MailService; use Core\User\Entity\User; @@ -11,7 +13,6 @@ use Core\User\Entity\UserResetPassword; use Core\User\Enum\UserResetPasswordStatusEnum; use Core\User\Enum\UserStatusEnum; -use Core\User\Service\UserAvatarService; use DateInterval; use DateTimeImmutable; use Laminas\Diactoros\UploadedFile; @@ -44,8 +45,8 @@ public function testRegisterAccountDuplicateIdentity(): void 'status' => UserStatusEnum::Pending, ]); - $userAvatarService = $this->createMock(UserAvatarService::class); - $this->replaceService(UserAvatarService::class, $userAvatarService); + $userAvatarService = $this->createMock(UserAvatarServiceInterface::class); + $this->replaceService(UserAvatarServiceInterface::class, $userAvatarService); $response = $this->post('/user/account', $this->getValidUserData(['status' => UserStatusEnum::Pending->value])); $this->assertResponseConflict($response); @@ -70,8 +71,8 @@ public function testRegisterAccountDuplicateEmail(): void 'status' => UserStatusEnum::Pending, ]); - $userAvatarService = $this->createMock(UserAvatarService::class); - $this->replaceService(UserAvatarService::class, $userAvatarService); + $userAvatarService = $this->createMock(UserAvatarServiceInterface::class); + $this->replaceService(UserAvatarServiceInterface::class, $userAvatarService); $response = $this->post('/user/account', $this->getValidUserData(['status' => UserStatusEnum::Pending->value])); $this->assertResponseConflict($response); @@ -91,8 +92,8 @@ public function testRegisterAccountDuplicateEmail(): void */ public function testRegisterAccount(): void { - $userAvatarService = $this->createMock(UserAvatarService::class); - $this->replaceService(UserAvatarService::class, $userAvatarService); + $userAvatarService = $this->createMock(UserAvatarServiceInterface::class); + $this->replaceService(UserAvatarServiceInterface::class, $userAvatarService); $mailService = $this->createMock(MailService::class); $this->replaceService(MailService::class, $mailService); @@ -135,7 +136,7 @@ public function testCreateMyAvatar(): void 'saveAvatarImage', ]) ->getMock(); - $this->replaceService(UserAvatarService::class, $userAvatarService); + $this->replaceService(UserAvatarServiceInterface::class, $userAvatarService); $user = $this->createUser(); $this->loginAs($user->getIdentity(), self::DEFAULT_PASSWORD); @@ -338,8 +339,8 @@ public function testRequestResetPasswordExpired(): void $this->replaceService(MailService::class, $mailService); $response = $this->patch('/user/account/reset-password/' . $resetPassword->getHash(), [ - 'password' => '654321', - 'passwordConfirm' => '654321', + 'password' => '87654321', + 'passwordConfirm' => '87654321', ]); $this->assertResponseGone($response); @@ -347,10 +348,7 @@ public function testRequestResetPasswordExpired(): void $this->assertArrayHasKey('error', $data); $this->assertArrayHasKey('messages', $data['error']); $this->assertNotEmpty($data['error']['messages'][0]); - $this->assertSame( - sprintf(Message::RESET_PASSWORD_EXPIRED, $resetPassword->getHash()), - $data['error']['messages'][0] - ); + $this->assertSame(Message::RESET_PASSWORD_EXPIRED, $data['error']['messages'][0]); } /** @@ -377,8 +375,8 @@ public function testRequestResetPasswordAlreadyUsed(): void $this->replaceService(MailService::class, $mailService); $response = $this->patch('/user/account/reset-password/' . $resetPassword->getHash(), [ - 'password' => '654321', - 'passwordConfirm' => '654321', + 'password' => '87654321', + 'passwordConfirm' => '87654321', ]); $this->assertResponseConflict($response); @@ -386,10 +384,7 @@ public function testRequestResetPasswordAlreadyUsed(): void $this->assertArrayHasKey('error', $data); $this->assertArrayHasKey('messages', $data['error']); $this->assertNotEmpty($data['error']['messages'][0]); - $this->assertSame( - sprintf(Message::RESET_PASSWORD_USED, $resetPassword->getHash()), - $data['error']['messages'][0] - ); + $this->assertSame(Message::RESET_PASSWORD_USED, $data['error']['messages'][0]); } /** @@ -416,8 +411,8 @@ public function testResetPassword(): void $this->replaceService(MailService::class, $mailService); $response = $this->patch('/user/account/reset-password/' . $resetPassword->getHash(), [ - 'password' => '654321', - 'passwordConfirm' => '654321', + 'password' => '87654321', + 'passwordConfirm' => '87654321', ]); $this->assertResponseOk($response); diff --git a/test/Unit/Admin/Service/AdminServiceTest.php b/test/Unit/Admin/Service/AdminServiceTest.php index 6d25f90a..43f02f12 100644 --- a/test/Unit/Admin/Service/AdminServiceTest.php +++ b/test/Unit/Admin/Service/AdminServiceTest.php @@ -4,12 +4,14 @@ namespace ApiTest\Unit\Admin\Service; +use Api\Admin\Service\AdminService; +use Api\Admin\Service\AdminServiceInterface; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Core\Admin\Enum\AdminStatusEnum; use Core\Admin\Repository\AdminRepository; use Core\Admin\Repository\AdminRoleRepository; -use Core\Admin\Service\AdminService; use Core\App\Exception\ConflictException; use Core\App\Message; use Exception; @@ -21,9 +23,9 @@ class AdminServiceTest extends TestCase { - private AdminService $adminService; - private AdminRepository|MockObject $adminRepository; - private AdminRoleRepository|MockObject $adminRoleRepository; + private AdminServiceInterface $adminService; + private AdminRepository&MockObject $adminRepository; + private AdminRoleRepository&MockObject $adminRoleRepository; /** * @throws \PHPUnit\Framework\MockObject\Exception @@ -48,7 +50,7 @@ public function testCreateAdminThrowsDuplicateIdentity(): void ->method('findOneBy') ->willReturn($this->getAdminEntity(['identity' => $request['identity']])); - $this->adminService->createAdmin($request); + $this->adminService->saveAdmin($request); } /** @@ -60,24 +62,21 @@ public function testCreateAdminSuperAdminRole(): void 'roles' => [ [ 'uuid' => 'uuid', - 'name' => AdminRole::ROLE_SUPERUSER, + 'name' => AdminRoleEnum::Superuser, ], ], ]); - $role = (new AdminRole())->setName(AdminRole::ROLE_SUPERUSER); + $role = (new AdminRole())->setName(AdminRoleEnum::Superuser); $this->adminRoleRepository->method('find')->willReturn($role); - $this->adminRepository->method('saveAdmin')->willReturn( - $this->getAdminEntity($data) - ); - $admin = $this->adminService->createAdmin($data); + $admin = $this->adminService->saveAdmin($data); $this->assertSame($data['identity'], $admin->getIdentity()); - $this->assertTrue(Admin::verifyPassword($data['password'], $admin->getPassword())); + $this->assertTrue($admin->verifyPassword($data['password'])); $this->assertCount(count($data['roles']), $admin->getRoles()); - $this->assertSame($role->getName(), ($admin->getRoles()->first())->getName()); + $this->assertSame($role->getName(), ($admin->getRoles()[0])->getName()); $this->assertSame(AdminStatusEnum::Active, $admin->getStatus()); } diff --git a/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php b/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php index fc205831..dcddcc26 100644 --- a/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php +++ b/test/Unit/App/Middleware/AuthenticationMiddlewareTest.php @@ -5,7 +5,7 @@ namespace ApiTest\Unit\App\Middleware; use Api\App\Middleware\AuthenticationMiddleware; -use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Laminas\Diactoros\ServerRequest; use Mezzio\Authentication\AuthenticationInterface; use Mezzio\Authentication\UserInterface; @@ -46,8 +46,8 @@ public function testAuthenticationFailsFallbackToGuestUser(): void ->willReturnCallback(function (ServerRequestInterface $request) { $user = $request->getAttribute(UserInterface::class); $this->assertInstanceOf(UserInterface::class, $user); - $this->assertSame(UserRole::ROLE_GUEST, $user->getIdentity()); - $this->assertSame(['guest'], $user->getRoles()); + $this->assertSame(UserRoleEnum::Guest->value, $user->getIdentity()); + $this->assertSame([UserRoleEnum::Guest], $user->getRoles()); $this->assertCount(1, $user->getRoles()); return $this->response; }); diff --git a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php index 8370a8e5..4e665f39 100644 --- a/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php +++ b/test/Unit/App/Middleware/AuthorizationMiddlewareTest.php @@ -7,11 +7,13 @@ use Api\App\Middleware\AuthorizationMiddleware; use Core\Admin\Entity\Admin; use Core\Admin\Entity\AdminRole; +use Core\Admin\Enum\AdminRoleEnum; use Core\Admin\Enum\AdminStatusEnum; use Core\Admin\Repository\AdminRepository; use Core\App\Message; use Core\User\Entity\User; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use Core\User\Repository\UserRepository; use Core\User\UserIdentity; @@ -27,7 +29,6 @@ use Psr\Http\Server\RequestHandlerInterface; use function json_decode; -use function sprintf; class AuthorizationMiddlewareTest extends TestCase { @@ -77,7 +78,7 @@ public function testAuthorizationInactiveAdmin(): void $user = (new Admin()) ->setIdentity('admin@dotkernel.com') ->setStatus(AdminStatusEnum::Inactive) - ->addRole((new AdminRole())->setName(AdminRole::ROLE_ADMIN)); + ->addRole((new AdminRole())->setName(AdminRoleEnum::Admin)); $this->adminRepository->method('findOneBy')->willReturn($user); $identity = new UserIdentity('admin@dotkernel.com', ['admin'], ['oauth_client_id' => 'admin']); @@ -89,7 +90,7 @@ public function testAuthorizationInactiveAdmin(): void $data = json_decode($response->getBody()->getContents(), true); $this->assertArrayHasKey('error', $data); $this->assertArrayHasKey('messages', $data['error']); - $this->assertContains(Message::ADMIN_NOT_ACTIVATED, $data['error']['messages']); + $this->assertContains(Message::ADMIN_INACTIVE, $data['error']['messages']); } public function testAuthorizationInactiveUser(): void @@ -123,10 +124,7 @@ public function testAuthorizationUserNotFoundOrDeleted(): void $data = json_decode($response->getBody()->getContents(), true); $this->assertArrayHasKey('error', $data); $this->assertArrayHasKey('messages', $data['error']); - $this->assertContains( - sprintf(Message::USER_NOT_FOUND_BY_IDENTITY, $identity->getIdentity()), - $data['error']['messages'] - ); + $this->assertContains(Message::USER_NOT_FOUND, $data['error']['messages']); } public function testAuthorizationNotGranted(): void @@ -134,7 +132,7 @@ public function testAuthorizationNotGranted(): void $user = (new User()) ->setIdentity('test@dotkernel.com') ->activate() - ->addRole((new UserRole())->setName(UserRole::ROLE_USER)); + ->addRole((new UserRole())->setName(UserRoleEnum::User)); $this->userRepository->method('findOneBy')->willReturn($user); $identity = new UserIdentity('test@dotkernel.com', ['user'], ['oauth_client_id' => 'frontend']); @@ -157,7 +155,7 @@ public function testAuthorizationAccessGranted(): void $user = (new User()) ->setIdentity('test@dotkernel.com') ->activate() - ->addRole((new UserRole())->setName(UserRole::ROLE_USER)); + ->addRole((new UserRole())->setName(UserRoleEnum::User)); $this->userRepository->method('findOneBy')->willReturn($user); $this->authorization->method('isGranted')->willReturn(true); diff --git a/test/Unit/User/Service/UserAvatarServiceTest.php b/test/Unit/User/Service/UserAvatarServiceTest.php index 7c5840e0..a6f03635 100644 --- a/test/Unit/User/Service/UserAvatarServiceTest.php +++ b/test/Unit/User/Service/UserAvatarServiceTest.php @@ -4,10 +4,11 @@ namespace ApiTest\Unit\User\Service; +use Api\User\Service\UserAvatarService; +use Api\User\Service\UserAvatarServiceInterface; use Core\User\Entity\User; use Core\User\Entity\UserAvatar; use Core\User\Repository\UserAvatarRepository; -use Core\User\Service\UserAvatarService; use Laminas\Diactoros\UploadedFile; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; @@ -15,7 +16,7 @@ class UserAvatarServiceTest extends TestCase { - private UserAvatarService|MockObject $subject; + private UserAvatarServiceInterface&MockObject $subject; private UploadedFile $uploadedFile; /** @@ -49,7 +50,7 @@ public function testCreateAvatarOverwrite(): void $this->subject->method('createFileName')->willReturn($fileName); $user = $this->getUser(); - $avatar = $this->subject->createAvatar($user, $this->uploadedFile); + $avatar = $this->subject->saveAvatar($user, $this->uploadedFile); $this->assertSame($fileName, $avatar->getName()); } @@ -62,7 +63,7 @@ public function testCreateAvatarDefault(): void $this->subject->method('createFileName')->willReturn($fileName); $user = new User(); - $avatar = $this->subject->createAvatar($user, $this->uploadedFile); + $avatar = $this->subject->saveAvatar($user, $this->uploadedFile); $this->assertSame($fileName, $avatar->getName()); } diff --git a/test/Unit/User/Service/UserServiceTest.php b/test/Unit/User/Service/UserServiceTest.php index 5d5d7326..67da141d 100644 --- a/test/Unit/User/Service/UserServiceTest.php +++ b/test/Unit/User/Service/UserServiceTest.php @@ -4,6 +4,8 @@ namespace ApiTest\Unit\User\Service; +use Api\User\Service\UserService; +use Api\User\Service\UserServiceInterface; use Core\App\Exception\BadRequestException; use Core\App\Exception\ConflictException; use Core\App\Exception\NotFoundException; @@ -12,11 +14,11 @@ use Core\User\Entity\User; use Core\User\Entity\UserDetail; use Core\User\Entity\UserRole; +use Core\User\Enum\UserRoleEnum; use Core\User\Enum\UserStatusEnum; use Core\User\Repository\UserDetailRepository; use Core\User\Repository\UserRepository; use Core\User\Repository\UserRoleRepository; -use Core\User\Service\UserService; use Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -26,7 +28,7 @@ class UserServiceTest extends TestCase { - private UserService $subject; + private UserServiceInterface $subject; private UserRepository&MockObject $userRepository; private UserDetailRepository&MockObject $userDetailRepository; private UserRoleRepository&MockObject $userRoleRepository; @@ -52,6 +54,7 @@ public function setUp(): void } /** + * @throws BadRequestException * @throws NotFoundException * @throws ConflictException */ @@ -64,7 +67,7 @@ public function testCreateUserThrowsExceptionDuplicateIdentity(): void $this->expectException(Exception::class); - $this->subject->createUser($request); + $this->subject->saveUser($request); } /** @@ -76,21 +79,18 @@ public function testCreateUserWithMultipleRoles(): void 'roles' => [ [ 'uuid' => 'uuid', - 'name' => UserRole::ROLE_GUEST, + 'name' => UserRoleEnum::Guest, ], [ 'uuid' => 'uuid', - 'name' => UserRole::ROLE_USER, + 'name' => UserRoleEnum::User, ], ], ]); $this->userRoleRepository->method('find')->willReturn(new UserRole()); - $this->userRepository->method('saveUser')->willReturn( - $this->getUserEntity($data) - ); - $user = $this->subject->createUser($data); + $user = $this->subject->saveUser($data); $this->assertCount(count($data['roles']), $user->getRoles()); } @@ -103,21 +103,18 @@ public function testCreateUserWithDefaultRole(): void 'roles' => [ [ 'uuid' => 'uuid', - 'name' => UserRole::ROLE_USER, + 'name' => UserRoleEnum::User, ], ], ]); - $defaultRole = (new UserRole())->setName(UserRole::ROLE_USER); + $defaultRole = (new UserRole())->setName(UserRoleEnum::User); $this->userRoleRepository->method('find')->willReturn($defaultRole); - $this->userRepository->method('saveUser')->willReturn( - $this->getUserEntity($data) - ); - $user = $this->subject->createUser($data); + $user = $this->subject->saveUser($data); $this->assertCount(1, $user->getRoles()); - $this->assertSame($defaultRole->getName(), ($user->getRoles()->first())->getName()); + $this->assertSame($defaultRole->getName(), ($user->getRoles()[0])->getName()); } /** @@ -126,15 +123,12 @@ public function testCreateUserWithDefaultRole(): void public function testCreateUser(): void { $this->userRoleRepository->method('find')->willReturn(new UserRole()); - $this->userRepository->method('saveUser')->willReturn( - $this->getUserEntity($this->getUser()) - ); $data = $this->getUser(); - $user = $this->subject->createUser($data); + $user = $this->subject->saveUser($data); $this->assertSame($data['identity'], $user->getIdentity()); - $this->assertTrue(User::verifyPassword($data['password'], $user->getPassword())); + $this->assertTrue($user->verifyPassword($data['password'])); $this->assertSame($data['detail']['firstName'], $user->getDetail()->getFirstName()); $this->assertSame($data['detail']['lastName'], $user->getDetail()->getLastName()); $this->assertSame($data['detail']['email'], $user->getDetail()->getEmail()); @@ -153,11 +147,11 @@ public function testUpdateUserThrowsExceptionDuplicateUserDetailEmail(): void $this->expectException(Exception::class); - $this->subject->updateUser($this->getUserEntity(), [ + $this->subject->saveUser([ 'detail' => [ 'email' => 'test@dotkernel.com', ], - ]); + ], $this->getUserEntity()); } /** @@ -167,8 +161,6 @@ public function testUpdateUser(): void { $user = $this->getUserEntity($this->getUser()); - $this->userRepository->method('saveUser')->willReturn($user); - $updateData = [ 'identity' => 'test@test.com', 'password' => '654321', @@ -179,9 +171,9 @@ public function testUpdateUser(): void ], ]; - $updatedUser = $this->subject->updateUser($user, $updateData); + $updatedUser = $this->subject->saveUser($updateData, $user); - $this->assertTrue(User::verifyPassword($updateData['password'], $updatedUser->getPassword())); + $this->assertTrue($updatedUser->verifyPassword($updateData['password'])); $this->assertSame($updateData['detail']['firstName'], $updatedUser->getDetail()->getFirstName()); $this->assertSame($updateData['detail']['lastName'], $updatedUser->getDetail()->getLastName()); $this->assertSame($updateData['detail']['email'], $updatedUser->getDetail()->getEmail()); @@ -198,7 +190,7 @@ private function getUser(array $data = []): array 'email' => 'test@dotkernel2.com', ], 'roles' => [ - ['uuid' => 'uuid', 'name' => UserRole::ROLE_USER], + ['uuid' => 'uuid', 'name' => UserRoleEnum::User], ], ];