From c61ef65d4d6c63570334405115b837f8f428d8ed Mon Sep 17 00:00:00 2001 From: Ilya Vasilenko Date: Thu, 20 Jun 2024 09:53:27 +0500 Subject: [PATCH] swagger --- app/composer.json | 7 +- app/composer.lock | 984 +++++++++++++++++- app/config/bundles.php | 3 + app/config/packages/nelmio_api_doc.yaml | 21 + app/config/packages/security.yaml | 1 + app/config/packages/twig.yaml | 6 + app/config/routes/nelmio_api_doc.yaml | 12 + app/src/Controller/AuthController.php | 19 + app/src/Controller/ProfileController.php | 41 + app/src/Listeners/AccessDeniedListener.php | 5 +- app/src/Listeners/JwtListener.php | 26 +- app/src/Response/ApiResponse.php | 107 -- app/src/Response/TokenResponse.php | 14 - .../Service/Action/ActionServiceInterface.php | 4 +- app/src/Service/Action/BaseActionService.php | 10 +- .../Action/Classes/CheckRecoveryCode.php | 12 +- .../Action/Classes/CheckRegisterCode.php | 12 +- .../Service/Action/Classes/DeleteProfile.php | 8 +- app/src/Service/Action/Classes/GetProfile.php | 20 +- .../Action/Classes/RecoveryProfile.php | 4 +- app/src/Service/Action/Classes/Register.php | 8 +- .../Action/Classes/SendRegisterCode.php | 4 +- app/src/Service/Dto/BaseDto.php | 2 + .../Service/Response/BaseResponseService.php | 20 - .../Response/Classes/ProfileResponse.php | 27 + app/src/Service/Response/Classes/Response.php | 145 ++- .../Response/Classes/TokenResponse.php | 19 + .../Response/ResponseServiceInterface.php | 10 +- .../Service/Send/Classes/CodeSendService.php | 8 +- app/symfony.lock | 29 + app/templates/base.html.twig | 16 + 31 files changed, 1387 insertions(+), 217 deletions(-) create mode 100644 app/config/packages/nelmio_api_doc.yaml create mode 100644 app/config/packages/twig.yaml create mode 100644 app/config/routes/nelmio_api_doc.yaml delete mode 100644 app/src/Response/ApiResponse.php delete mode 100644 app/src/Response/TokenResponse.php delete mode 100644 app/src/Service/Response/BaseResponseService.php create mode 100644 app/src/Service/Response/Classes/ProfileResponse.php create mode 100644 app/src/Service/Response/Classes/TokenResponse.php create mode 100644 app/templates/base.html.twig diff --git a/app/composer.json b/app/composer.json index 6ae3ba1..804133d 100644 --- a/app/composer.json +++ b/app/composer.json @@ -13,6 +13,8 @@ "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.2", "lexik/jwt-authentication-bundle": "^3.0", + "nelmio/api-doc-bundle": "^4.27", + "symfony/asset": "7.0.*", "symfony/cache": "7.0.*", "symfony/console": "7.0.*", "symfony/dotenv": "7.0.*", @@ -25,8 +27,11 @@ "symfony/runtime": "7.0.*", "symfony/security-bundle": "7.0.*", "symfony/serializer": "7.0.*", + "symfony/twig-bundle": "7.0.*", "symfony/validator": "7.0.*", - "symfony/yaml": "7.0.*" + "symfony/yaml": "7.0.*", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" }, "require-dev": { "roave/security-advisories": "dev-latest", diff --git a/app/composer.lock b/app/composer.lock index f3c8f9a..df47980 100644 --- a/app/composer.lock +++ b/app/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b38b50076633f562508d6896c57850ce", + "content-hash": "a47e42ecd6c25174dcfe545065022162", "packages": [ { "name": "doctrine/cache", @@ -1548,6 +1548,344 @@ ], "time": "2024-05-05T17:49:24+00:00" }, + { + "name": "nelmio/api-doc-bundle", + "version": "v4.27.0", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioApiDocBundle.git", + "reference": "221a1febaf861435b51c80cffd1a78efb4168345" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/221a1febaf861435b51c80cffd1a78efb4168345", + "reference": "221a1febaf861435b51c80cffd1a78efb4168345", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.4", + "phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0", + "phpdocumentor/type-resolver": "^1.8.2", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/container": "^1.0 || ^2.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/config": "^5.4 || ^6.4 || ^7.0", + "symfony/console": "^5.4 || ^6.4 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.4 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4.24 || ^6.4 || ^7.0", + "symfony/http-foundation": "^5.4 || ^6.4 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.4 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", + "symfony/property-info": "^5.4.10 || ^6.4 || ^7.0", + "symfony/routing": "^5.4 || ^6.4 || ^7.0", + "zircote/swagger-php": "^4.6.1" + }, + "conflict": { + "zircote/swagger-php": "4.8.7" + }, + "require-dev": { + "api-platform/core": "^2.7.0 || ^3", + "composer/package-versions-deprecated": "1.11.99.1", + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.52", + "friendsofsymfony/rest-bundle": "^2.8 || ^3.0", + "jms/serializer": "^1.14 || ^3.0", + "jms/serializer-bundle": "^2.3 || ^3.0 || ^4.0 || ^5.0", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.5", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^9.6 || ^10.5", + "symfony/asset": "^5.4 || ^6.4 || ^7.0", + "symfony/browser-kit": "^5.4 || ^6.4 || ^7.0", + "symfony/cache": "^5.4 || ^6.4 || ^7.0", + "symfony/dom-crawler": "^5.4 || ^6.4 || ^7.0", + "symfony/expression-language": "^5.4 || ^6.4 || ^7.0", + "symfony/form": "^5.4 || ^6.4 || ^7.0", + "symfony/phpunit-bridge": "^6.4", + "symfony/property-access": "^5.4 || ^6.4 || ^7.0", + "symfony/security-csrf": "^5.4 || ^6.4 || ^7.0", + "symfony/serializer": "^5.4 || ^6.4 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0", + "symfony/templating": "^5.4 || ^6.4 || ^7.0", + "symfony/twig-bundle": "^5.4 || ^6.4 || ^7.0", + "symfony/uid": "^5.4 || ^6.4 || ^7.0", + "symfony/validator": "^5.4 || ^6.4 || ^7.0", + "willdurand/hateoas-bundle": "^1.0 || ^2.0" + }, + "suggest": { + "api-platform/core": "For using an API oriented framework.", + "doctrine/annotations": "For using doctrine annotations", + "friendsofsymfony/rest-bundle": "For using the parameters annotations.", + "jms/serializer-bundle": "For describing your models.", + "symfony/asset": "For using the Swagger UI.", + "symfony/cache": "For using a PSR-6 compatible cache implementation with the API doc generator.", + "symfony/form": "For describing your form type models.", + "symfony/monolog-bundle": "For using a PSR-3 compatible logger implementation with the API PHP describer.", + "symfony/security-csrf": "For using csrf protection tokens in forms.", + "symfony/serializer": "For describing your models.", + "symfony/twig-bundle": "For using the Swagger UI.", + "symfony/validator": "For describing the validation constraints in your models.", + "willdurand/hateoas-bundle": "For extracting HATEOAS metadata." + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Nelmio\\ApiDocBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://github.com/nelmio/NelmioApiDocBundle/contributors" + } + ], + "description": "Generates documentation for your REST API from annotations and attributes", + "keywords": [ + "api", + "doc", + "documentation", + "rest" + ], + "support": { + "issues": "https://github.com/nelmio/NelmioApiDocBundle/issues", + "source": "https://github.com/nelmio/NelmioApiDocBundle/tree/v4.27.0" + }, + "time": "2024-06-12T23:47:19+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.4.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.1" + }, + "time": "2024-05-21T05:55:05+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "153ae662783729388a584b4361f2545e4d841e3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.0", + "php": "^7.3 || ^8.0", + "phpdocumentor/reflection-common": "^2.0", + "phpstan/phpdoc-parser": "^1.13" + }, + "require-dev": { + "ext-tokenizer": "*", + "phpbench/phpbench": "^1.2", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + }, + "time": "2024-02-23T11:10:43+00:00" + }, + { + "name": "phpstan/phpdoc-parser", + "version": "1.29.1", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpdoc-parser.git", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/annotations": "^2.0", + "nikic/php-parser": "^4.15", + "php-parallel-lint/php-parallel-lint": "^1.2", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", + "phpstan/phpstan-strict-rules": "^1.0", + "phpunit/phpunit": "^9.5", + "symfony/process": "^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPStan\\PhpDocParser\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPDoc parser with support for nullable, intersection and generic types", + "support": { + "issues": "https://github.com/phpstan/phpdoc-parser/issues", + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1" + }, + "time": "2024-05-31T08:52:43+00:00" + }, { "name": "psr/cache", "version": "3.0.0", @@ -1798,6 +2136,75 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "symfony/asset", + "version": "v7.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "0f106714bb8d857560edd2ada7f387d2f437c830" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/0f106714bb8d857560edd2ada7f387d2f437c830", + "reference": "0f106714bb8d857560edd2ada7f387d2f437c830", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/http-foundation": "<6.4" + }, + "require-dev": { + "symfony/http-client": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Asset\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Manages URL generation and versioning of web assets such as CSS stylesheets, JavaScript files and image files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset/tree/v7.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:55:39+00:00" + }, { "name": "symfony/cache", "version": "v7.0.8", @@ -3552,28 +3959,95 @@ "time": "2024-06-02T15:49:03+00:00" }, { - "name": "symfony/password-hasher", + "name": "symfony/options-resolver", "version": "v7.0.8", "source": { "type": "git", - "url": "https://github.com/symfony/password-hasher.git", - "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "19eecfc6f1b0e4b093db7f4a71eedc91843e711a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/25c66dba8ca72c9636b16e9a4b33d18554969a3f", - "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/19eecfc6f1b0e4b093db7f4a71eedc91843e711a", + "reference": "19eecfc6f1b0e4b093db7f4a71eedc91843e711a", "shasum": "" }, "require": { - "php": ">=8.2" - }, - "conflict": { - "symfony/security-core": "<6.4" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, - "require-dev": { - "symfony/console": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0" + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:55:39+00:00" + }, + { + "name": "symfony/password-hasher", + "version": "v7.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/25c66dba8ca72c9636b16e9a4b33d18554969a3f", + "reference": "25c66dba8ca72c9636b16e9a4b33d18554969a3f", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5096,6 +5570,198 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/twig-bridge", + "version": "v7.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "c8e05d7545962198df715d705c132de0674dc5b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/c8e05d7545962198df715d705c132de0674dc5b2", + "reference": "c8e05d7545962198df715d705c132de0674dc5b2", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<6.4", + "symfony/form": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.4", + "symfony/serializer": "<6.4", + "symfony/translation": "<6.4", + "symfony/workflow": "<6.4" + }, + "require-dev": { + "egulias/email-validator": "^2.1.10|^3|^4", + "league/html-to-markdown": "^5.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", + "symfony/asset": "^6.4|^7.0", + "symfony/asset-mapper": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/security-http": "^6.4|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/workflow": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0", + "twig/cssinliner-extra": "^2.12|^3", + "twig/inky-extra": "^2.12|^3", + "twig/markdown-extra": "^2.12|^3" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Twig with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bridge/tree/v7.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:55:39+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v7.0.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "a90e474bc260e59bed98a556db63673e6420a0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/a90e474bc260e59bed98a556db63673e6420a0be", + "reference": "a90e474bc260e59bed98a556db63673e6420a0be", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.2", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/twig-bridge": "^6.4|^7.0", + "twig/twig": "^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<6.4", + "symfony/translation": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Symfony\\Bundle\\TwigBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a tight integration of Twig into the Symfony full-stack framework", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/twig-bundle/tree/v7.0.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:55:39+00:00" + }, { "name": "symfony/validator", "version": "v7.0.8", @@ -5420,6 +6086,298 @@ } ], "time": "2024-04-28T11:44:19+00:00" + }, + { + "name": "twig/extra-bundle", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/cdc6e23aeb7f4953c1039568c3439aab60c56454", + "reference": "cdc6e23aeb7f4953c1039568c3439aab60c56454", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/framework-bundle": "^5.4|^6.4|^7.0", + "symfony/twig-bundle": "^5.4|^6.4|^7.0", + "twig/twig": "^3.0" + }, + "require-dev": { + "league/commonmark": "^1.0|^2.0", + "symfony/phpunit-bridge": "^6.4|^7.0", + "twig/cache-extra": "^3.0", + "twig/cssinliner-extra": "^3.0", + "twig/html-extra": "^3.0", + "twig/inky-extra": "^3.0", + "twig/intl-extra": "^3.0", + "twig/markdown-extra": "^3.0", + "twig/string-extra": "^3.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Twig\\Extra\\TwigExtraBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Symfony bundle for extra Twig extensions", + "homepage": "https://twig.symfony.com", + "keywords": [ + "bundle", + "extra", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/twig-extra-bundle/tree/v3.10.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-05-11T07:35:57+00:00" + }, + { + "name": "twig/twig", + "version": "v3.10.3", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/67f29781ffafa520b0bbfbd8384674b42db04572", + "reference": "67f29781ffafa520b0bbfbd8384674b42db04572", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php80": "^1.22" + }, + "require-dev": { + "psr/container": "^1.0|^2.0", + "symfony/phpunit-bridge": "^5.4.9|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Resources/core.php", + "src/Resources/debug.php", + "src/Resources/escaper.php", + "src/Resources/string_loader.php" + ], + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.10.3" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-05-16T10:04:27+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "4.10.0", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "2d983ce67b9eb7e18403ae7bc5e765f8ce7b8d56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/2d983ce67b9eb7e18403ae7bc5e765f8ce7b8d56", + "reference": "2d983ce67b9eb7e18403ae7bc5e765f8ce7b8d56", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.2", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": ">=2.2", + "symfony/yaml": ">=3.3" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^1.7 || ^2.0", + "friendsofphp/php-cs-fixer": "^2.17 || ^3.47.1", + "phpstan/phpstan": "^1.6", + "phpunit/phpunit": ">=8", + "vimeo/psalm": "^4.23" + }, + "suggest": { + "doctrine/annotations": "^1.7 || ^2.0" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/4.10.0" + }, + "time": "2024-06-06T22:42:02+00:00" } ], "packages-dev": [ diff --git a/app/config/bundles.php b/app/config/bundles.php index afcea0b..386a29b 100644 --- a/app/config/bundles.php +++ b/app/config/bundles.php @@ -7,4 +7,7 @@ return [ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true], + Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], ]; diff --git a/app/config/packages/nelmio_api_doc.yaml b/app/config/packages/nelmio_api_doc.yaml new file mode 100644 index 0000000..0e7eb67 --- /dev/null +++ b/app/config/packages/nelmio_api_doc.yaml @@ -0,0 +1,21 @@ +nelmio_api_doc: + use_validation_groups: true + documentation: + servers: + - url: http://127.0.0.1:8080 + description: Localhost + info: + title: Квесты + description: Тестовое задание + version: 1.0.0 + components: + securitySchemes: + Bearer: + type: http + scheme: bearer + bearerFormat: JWT + security: + - Bearer: [ ] + areas: # to filter documented areas + path_patterns: + - ^/api(?!/doc$) # Accepts routes under /api except /api/doc diff --git a/app/config/packages/security.yaml b/app/config/packages/security.yaml index 92236a8..e1644bc 100644 --- a/app/config/packages/security.yaml +++ b/app/config/packages/security.yaml @@ -43,6 +43,7 @@ security: # Note: Only the *first* access control that matches will be used access_control: - { path: ^/api/login, roles: PUBLIC_ACCESS } + - { path: ^/api/doc, roles: PUBLIC_ACCESS } - { path: ^/api/register, roles: PUBLIC_ACCESS } - { path: ^/api/register/send, roles: ROLE_USER } diff --git a/app/config/packages/twig.yaml b/app/config/packages/twig.yaml new file mode 100644 index 0000000..3f795d9 --- /dev/null +++ b/app/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/app/config/routes/nelmio_api_doc.yaml b/app/config/routes/nelmio_api_doc.yaml new file mode 100644 index 0000000..e160911 --- /dev/null +++ b/app/config/routes/nelmio_api_doc.yaml @@ -0,0 +1,12 @@ +# Expose your documentation as JSON swagger compliant +app.swagger: + path: /api/doc.json + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger } + +## Requires the Asset component and the Twig bundle +## $ composer require twig asset +app.swagger_ui: + path: /api/doc + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger_ui } \ No newline at end of file diff --git a/app/src/Controller/AuthController.php b/app/src/Controller/AuthController.php index 47b3178..72e3782 100644 --- a/app/src/Controller/AuthController.php +++ b/app/src/Controller/AuthController.php @@ -3,14 +3,30 @@ namespace App\Controller; use App\Service\Action\ActionServiceInterface; +use App\Service\Dto\Classes\RegisterCodeDto; +use App\Service\Dto\Classes\RegisterDto; +use App\Service\Response\Classes\Response; +use Nelmio\ApiDocBundle\Annotation\Model; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; +use OpenApi\Attributes as OA; #[Route('/api', name: 'api_')] +#[OA\Tag(name: 'Авторизация')] +#[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) +)] class AuthController extends AbstractController { #[Route('/register', name: 'register', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: RegisterDto::class)) + )] public function register( ActionServiceInterface $registerService ): JsonResponse @@ -27,6 +43,9 @@ class AuthController extends AbstractController } #[Route('/register/check', name: 'register_check', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: RegisterCodeDto::class)) + )] public function checkRegisterCode( ActionServiceInterface $checkRegisterService ): JsonResponse diff --git a/app/src/Controller/ProfileController.php b/app/src/Controller/ProfileController.php index 51e5442..75f10cf 100644 --- a/app/src/Controller/ProfileController.php +++ b/app/src/Controller/ProfileController.php @@ -3,14 +3,28 @@ namespace App\Controller; use App\Service\Action\ActionServiceInterface; +use App\Service\Dto\Classes\RecoveryCodeDto; +use App\Service\Dto\Classes\RecoveryDto; +use App\Service\Response\Classes\ProfileResponse; +use App\Service\Response\Classes\Response; +use Nelmio\ApiDocBundle\Annotation\Model; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Attribute\Route; +use OpenApi\Attributes as OA; #[Route('/api', name: 'api_')] +#[OA\Tag(name: 'Профиль')] class ProfileController extends AbstractController { #[Route('/profile', name: 'profile', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: ProfileResponse::class, groups: ["message", "data", "profile"]) + ) + )] public function profile( ActionServiceInterface $profileService ): JsonResponse @@ -19,6 +33,13 @@ class ProfileController extends AbstractController } #[Route('/profile/delete', name: 'profile_delete', methods: ['GET'])] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] public function deleteProfile( ActionServiceInterface $deleteProfileService, ): JsonResponse @@ -27,6 +48,16 @@ class ProfileController extends AbstractController } #[Route('/profile/recovery', name: 'profile_recovery', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: RecoveryDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] public function recoveryProfile( ActionServiceInterface $recoveryProfileService, ): JsonResponse @@ -35,6 +66,16 @@ class ProfileController extends AbstractController } #[Route('/profile/recovery/check', name: 'profile_recovery_check', methods: ['POST'])] + #[OA\RequestBody( + content: new OA\JsonContent(ref: new Model(type: RecoveryCodeDto::class)) + )] + #[OA\Response( + response: 200, + description: 'Ответ', + content: new OA\JsonContent( + ref: new Model(type: Response::class, groups: ["message"]) + ) + )] public function recoveryCodeProfile( ActionServiceInterface $checkRecoveryService, ): JsonResponse diff --git a/app/src/Listeners/AccessDeniedListener.php b/app/src/Listeners/AccessDeniedListener.php index 8299af1..7a7aa3f 100644 --- a/app/src/Listeners/AccessDeniedListener.php +++ b/app/src/Listeners/AccessDeniedListener.php @@ -2,7 +2,6 @@ namespace App\Listeners; -use App\Response\ApiResponse; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\ExceptionEvent; @@ -21,7 +20,7 @@ class AccessDeniedListener implements EventSubscriberInterface public function onAccessException(ExceptionEvent $event): void { - $response = new ApiResponse(); + $response = new \App\Service\Response\Classes\Response(); $response->setStatusCode(Response::HTTP_FORBIDDEN); $response->addError('Доступ запрещен'); @@ -29,6 +28,6 @@ class AccessDeniedListener implements EventSubscriberInterface if (!$exception instanceof AccessDeniedException) { return; } - $event->setResponse($response); + $event->setResponse($response->getResponse()); } } \ No newline at end of file diff --git a/app/src/Listeners/JwtListener.php b/app/src/Listeners/JwtListener.php index 625ee5f..07e56ad 100644 --- a/app/src/Listeners/JwtListener.php +++ b/app/src/Listeners/JwtListener.php @@ -3,8 +3,7 @@ namespace App\Listeners; use App\Entity\User; -use App\Response\ApiResponse; -use App\Response\TokenResponse; +use App\Service\Response\Classes\TokenResponse; use JsonException; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationFailureEvent; use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent; @@ -31,16 +30,15 @@ class JwtListener return; } + $response = new TokenResponse(); + if ($user->isDeleted()) { - $response = new ApiResponse(); $response->addError('Пользователь удален'); } else { - $response = new TokenResponse(); $response->setToken($data['token']); - $response->addMessage('Здравствуйте, ' . $user->getFullName()); } - $data = json_decode($response->getContent(), true, 512, JSON_THROW_ON_ERROR); + $data = json_decode($response->getResponse()->getContent(), true, 512, JSON_THROW_ON_ERROR); $event->setData($data); } @@ -49,9 +47,9 @@ class JwtListener */ public function onAuthenticationFailureResponse(AuthenticationFailureEvent $event): void { - $response = new ApiResponse(); + $response = new \App\Service\Response\Classes\Response(); $response->addError('Неверный email или пароль'); - $event->setResponse($response); + $event->setResponse($response->getResponse()); } /** @@ -59,11 +57,11 @@ class JwtListener */ public function onJWTInvalid(JWTInvalidEvent $event): void { - $response = new ApiResponse(); + $response = new \App\Service\Response\Classes\Response(); $response->addError('Неверный токен авторизации'); $response->setStatusCode(Response::HTTP_FORBIDDEN); - $event->setResponse($response); + $event->setResponse($response->getResponse()); } /** @@ -71,11 +69,11 @@ class JwtListener */ public function onJWTNotFound(JWTNotFoundEvent $event): void { - $response = new ApiResponse(); + $response = new \App\Service\Response\Classes\Response(); $response->addError('Отсутствует токен'); $response->setStatusCode(Response::HTTP_FORBIDDEN); - $event->setResponse($response); + $event->setResponse($response->getResponse()); } /** @@ -83,10 +81,10 @@ class JwtListener */ public function onJWTExpired(JWTExpiredEvent $event): void { - $response = new ApiResponse(); + $response = new \App\Service\Response\Classes\Response(); $response->addError('Срок действия вашего токена истек, пожалуйста, обновите его'); $response->setStatusCode(Response::HTTP_FORBIDDEN); - $event->setResponse($response); + $event->setResponse($response->getResponse()); } } \ No newline at end of file diff --git a/app/src/Response/ApiResponse.php b/app/src/Response/ApiResponse.php deleted file mode 100644 index 9e36975..0000000 --- a/app/src/Response/ApiResponse.php +++ /dev/null @@ -1,107 +0,0 @@ -setResult(); - } - - /** - * Добавление ошибки - * - * @param string $message - * - * @return self - */ - public function addError(string $message): self - { - $this->status = false; - return $this->addMessage($message); - } - - /** - * Добавление ошибок - * - * @param array $errors - * - * @return $this - */ - public function addErrors(array $errors): self - { - $this->status = false; - foreach ($errors as $error) { - $this->addError($error); - } - - return $this; - } - - /** - * Добавление сообщения - * - * @param string $message - * - * @return self - */ - public function addMessage(string $message): self - { - $this->messages[] = $message; - return $this->setResult(); - } - - /** - * Запись контента ответа - * - * @param array|null $responseData - * - * @return void - */ - public function setResponseData(?array $responseData): void - { - $this->responseData = $responseData; - - $this->setResult(); - } - - /** - * @return bool - */ - public function isSuccess(): bool - { - return $this->status; - } - - /** - * Установка результата - * - * @return self - */ - protected function setResult(): self - { - $result = [ - 'status' => $this->status, - ]; - - if (!empty($this->responseData)) { - $result['data'] = $this->responseData; - } - if (!isset($result['data'])) { - $result['message'] = implode(', ', $this->messages); - } - - return $this->setData($result); - } -} \ No newline at end of file diff --git a/app/src/Response/TokenResponse.php b/app/src/Response/TokenResponse.php deleted file mode 100644 index a941d6c..0000000 --- a/app/src/Response/TokenResponse.php +++ /dev/null @@ -1,14 +0,0 @@ -setResponseData([ - 'token' => $token, - ]); - return $this; - } -} \ No newline at end of file diff --git a/app/src/Service/Action/ActionServiceInterface.php b/app/src/Service/Action/ActionServiceInterface.php index cec8a68..0f16176 100644 --- a/app/src/Service/Action/ActionServiceInterface.php +++ b/app/src/Service/Action/ActionServiceInterface.php @@ -2,11 +2,11 @@ namespace App\Service\Action; -use App\Response\ApiResponse; +use Symfony\Component\HttpFoundation\JsonResponse; interface ActionServiceInterface { - public function getResponse(): ApiResponse; + public function getResponse(): JsonResponse; public function runAction(): void; diff --git a/app/src/Service/Action/BaseActionService.php b/app/src/Service/Action/BaseActionService.php index a2007b7..97235c5 100644 --- a/app/src/Service/Action/BaseActionService.php +++ b/app/src/Service/Action/BaseActionService.php @@ -2,9 +2,9 @@ namespace App\Service\Action; -use App\Response\ApiResponse; -use App\Service\Dto\DtoServiceInterface; +use App\Service\Response\Classes\Response; use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\HttpFoundation\JsonResponse; abstract class BaseActionService implements ActionServiceInterface { @@ -17,7 +17,7 @@ abstract class BaseActionService implements ActionServiceInterface $this->responseService = $baseResponseService; } - public function getResponse(): ApiResponse + public function getResponse(): JsonResponse { if ($this->validate()) { $this->runAction(); @@ -27,8 +27,8 @@ abstract class BaseActionService implements ActionServiceInterface return $this->responseService->getResponse(); } - $response = new ApiResponse(); + $response = new Response(); $response->addError('Ошибка получения ответа'); - return $response; + return $response->getResponse(); } } \ No newline at end of file diff --git a/app/src/Service/Action/Classes/CheckRecoveryCode.php b/app/src/Service/Action/Classes/CheckRecoveryCode.php index fa1c5ff..4a1d3a0 100644 --- a/app/src/Service/Action/Classes/CheckRecoveryCode.php +++ b/app/src/Service/Action/Classes/CheckRecoveryCode.php @@ -38,7 +38,7 @@ class CheckRecoveryCode extends BaseActionService $code = $dto->code; $registerCode = $userExists->getRegisterCode(); if ($registerCode === null) { - $this->response->getResponse()->addError('Код подтверждения не отправлен'); + $this->response->addError('Код подтверждения не отправлен'); } else { if ($registerCodeDate = $registerCode->getDate()) { if ($registerCode->getCode() === $code && $currentDate->getTimestamp() < $registerCodeDate->getTimestamp()) { @@ -48,19 +48,19 @@ class CheckRecoveryCode extends BaseActionService $em->persist($userExists); $em->remove($registerCode); $em->flush(); - $this->response->getResponse()->addMessage('Профиль восстановлен'); + $this->response->addMessage('Профиль восстановлен'); } catch (\Exception $exception) { - $this->response->getResponse()->addError('Ошибка восстановления профиля'); + $this->response->addError('Ошибка восстановления профиля'); } } else { - $this->response->getResponse()->addError('Код недействителен'); + $this->response->addError('Код недействителен'); } } else { - $this->response->getResponse()->addError('Код недействителен'); + $this->response->addError('Код недействителен'); } } } else { - $this->response->getResponse()->addError('Пользователь не найден'); + $this->response->addError('Пользователь не найден'); } } diff --git a/app/src/Service/Action/Classes/CheckRegisterCode.php b/app/src/Service/Action/Classes/CheckRegisterCode.php index ed6f147..6411a73 100644 --- a/app/src/Service/Action/Classes/CheckRegisterCode.php +++ b/app/src/Service/Action/Classes/CheckRegisterCode.php @@ -42,7 +42,7 @@ class CheckRegisterCode extends BaseActionService $code = $this->registerCodeDto->getClass()->code; $registerCode = $this->user->getRegisterCode(); if ($registerCode === null) { - $this->response->getResponse()->addError('Код подтверждения не отправлен'); + $this->response->addError('Код подтверждения не отправлен'); } else { if ($registerCodeDate = $registerCode->getDate()) { if ($registerCode->getCode() === $code && $currentDate->getTimestamp() < $registerCodeDate->getTimestamp()) { @@ -52,15 +52,15 @@ class CheckRegisterCode extends BaseActionService $em->persist($this->user); $em->remove($registerCode); $em->flush(); - $this->response->getResponse()->addMessage('Регистрация подтверждена'); + $this->response->addMessage('Регистрация подтверждена'); } catch (\Exception $exception) { - $this->response->getResponse()->addError('Ошибка подтверждения регистрации'); + $this->response->addError('Ошибка подтверждения регистрации'); } } else { - $this->response->getResponse()->addError('Код недействителен'); + $this->response->addError('Код недействителен'); } } else { - $this->response->getResponse()->addError('Код недействителен'); + $this->response->addError('Код недействителен'); } } } @@ -68,7 +68,7 @@ class CheckRegisterCode extends BaseActionService public function validate(): bool { if ($this->user === null) { - $this->response->getResponse()->addError('Вы не авторизованы'); + $this->response->addError('Вы не авторизованы'); return false; } return $this->registerCodeDto->validate($this->response); diff --git a/app/src/Service/Action/Classes/DeleteProfile.php b/app/src/Service/Action/Classes/DeleteProfile.php index 01f042c..9408091 100644 --- a/app/src/Service/Action/Classes/DeleteProfile.php +++ b/app/src/Service/Action/Classes/DeleteProfile.php @@ -34,21 +34,21 @@ class DeleteProfile extends BaseActionService $em = $this->doctrine->getManager(); $em->persist($this->user); $em->flush(); - $this->response->getResponse()->addMessage('Профиль удален'); + $this->response->addMessage('Профиль удален'); } catch (\Exception $exception) { - $this->response->getResponse()->addError('Ошибка удаления профиля'); + $this->response->addError('Ошибка удаления профиля'); } } public function validate(): bool { if ($this->user === null) { - $this->response->getResponse()->addError('Вы не авторизованы'); + $this->response->addError('Вы не авторизованы'); return false; } if ($this->user->isDeleted()) { - $this->response->getResponse()->addError('Профиль уже удален'); + $this->response->addError('Профиль уже удален'); return false; } return true; diff --git a/app/src/Service/Action/Classes/GetProfile.php b/app/src/Service/Action/Classes/GetProfile.php index 0c39923..73f5526 100644 --- a/app/src/Service/Action/Classes/GetProfile.php +++ b/app/src/Service/Action/Classes/GetProfile.php @@ -4,6 +4,7 @@ namespace App\Service\Action\Classes; use App\Entity\User; use App\Service\Action\BaseActionService; +use App\Service\Response\Classes\ProfileResponse; use App\Service\Response\ResponseServiceInterface; use Symfony\Bundle\SecurityBundle\Security; use Symfony\Component\Security\Http\Attribute\CurrentUser; @@ -13,14 +14,18 @@ class GetProfile extends BaseActionService { private ?User $user; + /** + * @param ProfileResponse $profileResponse + * @param Security $security + * @param SerializerInterface $serializer + */ public function __construct( - private ResponseServiceInterface $response, - Security $security, - private SerializerInterface $serializer + private ResponseServiceInterface $profileResponse, + Security $security ) { $this->user = $security->getUser(); - parent::__construct($response); + parent::__construct($profileResponse); } @@ -33,12 +38,15 @@ class GetProfile extends BaseActionService */ public function runAction(): void { - $serializedUser = $this->serializer->serialize($this->user, 'json', ['groups' => ['profile']]); - $this->response->getResponse()->setResponseData(json_decode($serializedUser, true, 512, JSON_THROW_ON_ERROR)); + $this->profileResponse->setData($this->user); } public function validate(): bool { + if ($this->user === null) { + $this->profileResponse->addError('Вы не авторизованы'); + return false; + } return $this->user->isConfirm() && !$this->user->isDeleted(); } } \ No newline at end of file diff --git a/app/src/Service/Action/Classes/RecoveryProfile.php b/app/src/Service/Action/Classes/RecoveryProfile.php index 9cbf1c2..d6e7e68 100644 --- a/app/src/Service/Action/Classes/RecoveryProfile.php +++ b/app/src/Service/Action/Classes/RecoveryProfile.php @@ -42,14 +42,14 @@ class RecoveryProfile extends BaseActionService if ($userExists !== null) { if (!$userExists->isDeleted()) { - $this->response->getResponse()->addError('Профиль не удален'); + $this->response->addError('Профиль не удален'); } else { $this->recoveryCodeSendService->setUser($userExists); $this->recoveryCodeSendService->setResponse($this->response); $this->recoveryCodeSendService->send(); } } else { - $this->response->getResponse()->addError('Пользователь не найден'); + $this->response->addError('Пользователь не найден'); } } diff --git a/app/src/Service/Action/Classes/Register.php b/app/src/Service/Action/Classes/Register.php index 88eebcd..4dd29cd 100644 --- a/app/src/Service/Action/Classes/Register.php +++ b/app/src/Service/Action/Classes/Register.php @@ -48,7 +48,7 @@ class Register extends BaseActionService ->findOneByUniq($user->getEmail(), $user->getPhoneNumber()); if ($userExists) { - $this->response->getResponse()->addError('Пользователь уже существует'); + $this->response->addError('Пользователь уже существует'); } else { try { $user->setDeleted(false); @@ -63,13 +63,13 @@ class Register extends BaseActionService $em->persist($user); $em->flush(); - $this->response->getResponse()->addMessage('Пользователь зарегистрирован'); + $this->response->addMessage('Пользователь зарегистрирован'); $this->registerCodeSendService->setUser($user); $this->registerCodeSendService->setResponse($this->response); $this->registerCodeSendService->send(); } catch (\Exception $exception) { - $this->response->getResponse()->addError('Ошибка регистрации пользователя'); + $this->response->addError('Ошибка регистрации пользователя'); } } @@ -111,7 +111,7 @@ class Register extends BaseActionService } } } else { - $this->response->getResponse()->addError('Ошибка получения данных'); + $this->response->addError('Ошибка получения данных'); } return $user; diff --git a/app/src/Service/Action/Classes/SendRegisterCode.php b/app/src/Service/Action/Classes/SendRegisterCode.php index d6d5d4c..27666d3 100644 --- a/app/src/Service/Action/Classes/SendRegisterCode.php +++ b/app/src/Service/Action/Classes/SendRegisterCode.php @@ -47,11 +47,11 @@ class SendRegisterCode extends BaseActionService public function validate(): bool { if ($this->user === null) { - $this->response->getResponse()->addError('Вы не авторизованы'); + $this->response->addError('Вы не авторизованы'); return false; } if ($this->user->isConfirm()) { - $this->response->getResponse()->addError('Учетная запись уже подтверждена'); + $this->response->addError('Учетная запись уже подтверждена'); return false; } diff --git a/app/src/Service/Dto/BaseDto.php b/app/src/Service/Dto/BaseDto.php index 600c2be..47a3786 100644 --- a/app/src/Service/Dto/BaseDto.php +++ b/app/src/Service/Dto/BaseDto.php @@ -6,6 +6,7 @@ use App\Service\Response\ResponseServiceInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\Serializer\Annotation\Ignore; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer; @@ -33,6 +34,7 @@ abstract class BaseDto implements DtoServiceInterface * * @return DtoServiceInterface|null */ + #[Ignore] public function getClass(): ?DtoServiceInterface { if ($this->request) { diff --git a/app/src/Service/Response/BaseResponseService.php b/app/src/Service/Response/BaseResponseService.php deleted file mode 100644 index 6c97ee8..0000000 --- a/app/src/Service/Response/BaseResponseService.php +++ /dev/null @@ -1,20 +0,0 @@ -response = new ApiResponse(); - } - - public function getResponse(): ApiResponse - { - return $this->response; - } -} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/ProfileResponse.php b/app/src/Service/Response/Classes/ProfileResponse.php new file mode 100644 index 0000000..91c773c --- /dev/null +++ b/app/src/Service/Response/Classes/ProfileResponse.php @@ -0,0 +1,27 @@ +data = $user; + + return $this; + } + + public function getGroups(): array + { + return ['profile']; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/Classes/Response.php b/app/src/Service/Response/Classes/Response.php index 390215f..cf0ffde 100644 --- a/app/src/Service/Response/Classes/Response.php +++ b/app/src/Service/Response/Classes/Response.php @@ -2,9 +2,150 @@ namespace App\Service\Response\Classes; -use App\Service\Response\BaseResponseService; +use App\Service\Response\ResponseServiceInterface; +use Symfony\Component\HttpFoundation\JsonResponse; +use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor; +use Symfony\Component\Serializer\Annotation\Groups; +use Symfony\Component\Serializer\Annotation\Ignore; +use Symfony\Component\Serializer\Encoder\JsonEncoder; +use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory; +use Symfony\Component\Serializer\Mapping\Loader\AttributeLoader; +use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; +use Symfony\Component\Serializer\Normalizer\ObjectNormalizer; +use Symfony\Component\Serializer\Serializer; -class Response extends BaseResponseService +class Response implements ResponseServiceInterface { + /** + * @var bool + */ + #[Groups(["message", "data"])] + public bool $status = true; + /** + * @var string + */ + #[Groups(["message"])] + public string $message = ''; + + #[Ignore] + public int $statusCode = 200; + + #[Ignore] + private array $messages = []; + + #[Ignore] + private array $errors = []; + + #[Ignore] + private JsonResponse $response; + + public function __construct() + { + $this->response = new JsonResponse(); + } + + /** + * Группы сериализации + * + * @return array + */ + #[Ignore] + protected function getGroups(): array + { + return []; + } + + /** + * Добавление ошибки + * + * @param string $message + * + * @return $this + * + * @throws \JsonException + */ + #[Ignore] + public function addError(string $message): self + { + $this->errors[] = $message; + + return $this; + } + + /** + * Добавление сообщения + * + * @param string $message + * + * @return $this + * + * @throws \JsonException + */ + #[Ignore] + public function addMessage(string $message): self + { + $this->messages[] = $message; + + return $this; + } + + #[Ignore] + public function isSuccess(): bool + { + if (!empty($this->errors)) { + $this->status = false; + } else { + $this->status = true; + } + + return $this->status; + } + + #[Ignore] + public function getResponse(): JsonResponse + { + $this->refresh(); + return $this->response; + } + + #[Ignore] + public function setStatusCode(int $code): self + { + $this->statusCode = $code; + return $this; + } + + #[Ignore] + protected function refresh(): self + { + $groups = ['message']; + if (!empty($this->errors)) { + $this->status = false; + } else { + $this->status = true; + } + + $this->message = implode(', ', array_merge($this->messages, $this->errors)); + + if (isset($this->data) && !empty($this->data)) { + $groups = ['data']; + $groups = array_merge($groups, $this->getGroups()); + } + + $normalizer = new ObjectNormalizer( + new ClassMetadataFactory(new AttributeLoader()), + new CamelCaseToSnakeCaseNameConverter(), + null, + new ReflectionExtractor() + ); + $serializer = new Serializer([$normalizer], [new JsonEncoder()]); + $dataStr = $serializer->serialize($this, 'json', ['groups' => $groups]); + $dataArray = json_decode($dataStr, true, 512, JSON_THROW_ON_ERROR); + + $this->response->setData($dataArray); + $this->response->setStatusCode($this->statusCode); + + return $this; + } } \ No newline at end of file diff --git a/app/src/Service/Response/Classes/TokenResponse.php b/app/src/Service/Response/Classes/TokenResponse.php new file mode 100644 index 0000000..8ce5a95 --- /dev/null +++ b/app/src/Service/Response/Classes/TokenResponse.php @@ -0,0 +1,19 @@ +data = ['token' => $token]; + } +} \ No newline at end of file diff --git a/app/src/Service/Response/ResponseServiceInterface.php b/app/src/Service/Response/ResponseServiceInterface.php index d291c5a..e20fdd5 100644 --- a/app/src/Service/Response/ResponseServiceInterface.php +++ b/app/src/Service/Response/ResponseServiceInterface.php @@ -2,9 +2,15 @@ namespace App\Service\Response; -use App\Response\ApiResponse; +use Symfony\Component\HttpFoundation\JsonResponse; interface ResponseServiceInterface { - public function getResponse(): ApiResponse; + public function getResponse(): JsonResponse; + + public function addError(string $message): self; + + public function addMessage(string $message): self; + + public function isSuccess(): bool; } \ No newline at end of file diff --git a/app/src/Service/Send/Classes/CodeSendService.php b/app/src/Service/Send/Classes/CodeSendService.php index b35d49f..d8b5e4f 100644 --- a/app/src/Service/Send/Classes/CodeSendService.php +++ b/app/src/Service/Send/Classes/CodeSendService.php @@ -47,7 +47,7 @@ class CodeSendService implements SendServiceInterface public function send(): void { if ($this->user === null) { - $this->response->getResponse()->addError('Письмо не отправлено, пользователь не получен'); + $this->response->addError('Письмо не отправлено, пользователь не получен'); return; } $serializedUser = $this->serializer->serialize($this->user, 'json', ['groups' => ['profile']]); @@ -69,7 +69,7 @@ class CodeSendService implements SendServiceInterface $date = $codeObj->getDate(); $time = $date?->diff(new \DateTime()); } catch (\Exception $exception) { - $this->response->getResponse()->addError('Ошибка генерации кода'); + $this->response->addError('Ошибка генерации кода'); } if ($code) { @@ -83,9 +83,9 @@ class CodeSendService implements SendServiceInterface $this->sendService->setSubject($this->formatSubject($values)); $this->sendService->setBody($this->formatBody($values)); $this->sendService->send(); - $this->response->getResponse()->addMessage('Письмо с кодом отправлено'); + $this->response->addMessage('Письмо с кодом отправлено'); } else { - $this->response->getResponse()->addError('Ошибка генерации кода'); + $this->response->addError('Ошибка генерации кода'); } } diff --git a/app/symfony.lock b/app/symfony.lock index 9ef8d81..6babe26 100644 --- a/app/symfony.lock +++ b/app/symfony.lock @@ -38,6 +38,19 @@ "config/packages/lexik_jwt_authentication.yaml" ] }, + "nelmio/api-doc-bundle": { + "version": "4.27", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "3.0", + "ref": "c8e0c38e1a280ab9e37587a8fa32b251d5bc1c94" + }, + "files": [ + "config/packages/nelmio_api_doc.yaml", + "config/routes/nelmio_api_doc.yaml" + ] + }, "symfony/console": { "version": "7.0", "recipe": { @@ -140,6 +153,19 @@ "config/routes/security.yaml" ] }, + "symfony/twig-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, "symfony/validator": { "version": "7.0", "recipe": { @@ -151,5 +177,8 @@ "files": [ "config/packages/validator.yaml" ] + }, + "twig/extra-bundle": { + "version": "v3.10.0" } } diff --git a/app/templates/base.html.twig b/app/templates/base.html.twig new file mode 100644 index 0000000..1069c14 --- /dev/null +++ b/app/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + -- GitLab