From 0fedfc78bd25549a9ade40d006d2d46304aebeab Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Sat, 24 Aug 2024 21:25:59 +0300 Subject: [PATCH 01/10] #2 added api doc bundle --- composer.json | 1 + composer.lock | 272 +++++++++++++++++++++++++++- config/bundles.php | 1 + config/packages/nelmio_api_doc.yaml | 9 + config/routes/nelmio_api_doc.yaml | 12 ++ symfony.lock | 13 ++ 6 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 config/packages/nelmio_api_doc.yaml create mode 100644 config/routes/nelmio_api_doc.yaml diff --git a/composer.json b/composer.json index 6db4baa..facdf1f 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.2", "moneyphp/money": "^4.5", + "nelmio/api-doc-bundle": "^4.29", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", "symfony/console": "6.4.*", diff --git a/composer.lock b/composer.lock index 09f912e..79a40be 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "318289b31d9dca4fc46e0d61db84b98b", + "content-hash": "3e6c98618f2c9241dacaf4bf00c03c33", "packages": [ { "name": "doctrine/cache", @@ -1316,6 +1316,128 @@ }, "time": "2024-02-15T19:47:21+00:00" }, + { + "name": "nelmio/api-doc-bundle", + "version": "v4.29.3", + "source": { + "type": "git", + "url": "https://github.com/nelmio/NelmioApiDocBundle.git", + "reference": "61a3f8bb95111fade6eace55c071c43da0cc75d9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/61a3f8bb95111fade6eace55c071c43da0cc75d9", + "reference": "61a3f8bb95111fade6eace55c071c43da0cc75d9", + "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.29.3" + }, + "funding": [ + { + "url": "https://github.com/DjordyKoert", + "type": "github" + } + ], + "time": "2024-08-17T13:03:07+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -3628,6 +3750,73 @@ ], "time": "2024-06-24T14:04:31+00:00" }, + { + "name": "symfony/options-resolver", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "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/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.30.0", @@ -5213,6 +5402,87 @@ "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.6", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/e462ff5269ea0ec91070edd5d51dc7215bdea3b6", + "reference": "e462ff5269ea0ec91070edd5d51dc7215bdea3b6", + "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.6" + }, + "time": "2024-07-26T03:04:43+00:00" } ], "packages-dev": [ diff --git a/config/bundles.php b/config/bundles.php index fd50f83..e05d2dc 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -6,4 +6,5 @@ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], + Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], ]; diff --git a/config/packages/nelmio_api_doc.yaml b/config/packages/nelmio_api_doc.yaml new file mode 100644 index 0000000..4e68479 --- /dev/null +++ b/config/packages/nelmio_api_doc.yaml @@ -0,0 +1,9 @@ +nelmio_api_doc: + documentation: + info: + title: My App + description: This is an awesome app! + version: 1.0.0 + areas: # to filter documented areas + path_patterns: + - ^/api(?!/doc$) # Accepts routes under /api except /api/doc diff --git a/config/routes/nelmio_api_doc.yaml b/config/routes/nelmio_api_doc.yaml new file mode 100644 index 0000000..364b4af --- /dev/null +++ b/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 } diff --git a/symfony.lock b/symfony.lock index ab29576..8b9cf17 100644 --- a/symfony.lock +++ b/symfony.lock @@ -38,6 +38,19 @@ "migrations/.gitignore" ] }, + "nelmio/api-doc-bundle": { + "version": "4.29", + "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" + ] + }, "phpunit/phpunit": { "version": "9.6", "recipe": { From 826afde8dddc935f3507d8493aaa828c91f4dd6c Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Sat, 24 Aug 2024 21:58:50 +0300 Subject: [PATCH 02/10] #2 twig and assets for swagger ui --- composer.json | 6 +- composer.lock | 418 +++++++++++++++++++++++++++++- config/bundles.php | 2 + config/packages/twig.yaml | 6 + config/routes/nelmio_api_doc.yaml | 8 +- symfony.lock | 16 ++ templates/base.html.twig | 16 ++ 7 files changed, 466 insertions(+), 6 deletions(-) create mode 100644 config/packages/twig.yaml create mode 100644 templates/base.html.twig diff --git a/composer.json b/composer.json index facdf1f..ffb120c 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "nelmio/api-doc-bundle": "^4.29", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", + "symfony/asset": "6.4.*", "symfony/console": "6.4.*", "symfony/doctrine-messenger": "6.4.*", "symfony/dotenv": "6.4.*", @@ -26,9 +27,12 @@ "symfony/property-info": "6.4.*", "symfony/runtime": "6.4.*", "symfony/serializer": "6.4.*", + "symfony/twig-bundle": "6.4.*", "symfony/uid": "6.4.*", "symfony/validator": "6.4.*", - "symfony/yaml": "6.4.*" + "symfony/yaml": "6.4.*", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 79a40be..48d8bec 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3e6c98618f2c9241dacaf4bf00c03c33", + "content-hash": "a2842e63c541fbc15ba099dc54da74ca", "packages": [ { "name": "doctrine/cache", @@ -1910,6 +1910,75 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "symfony/asset", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/asset.git", + "reference": "c668aa320e26b7379540368832b9d1dd43d32603" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/asset/zipball/c668aa320e26b7379540368832b9d1dd43d32603", + "reference": "c668aa320e26b7379540368832b9d1dd43d32603", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "conflict": { + "symfony/http-foundation": "<5.4" + }, + "require-dev": { + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^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/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, { "name": "symfony/cache", "version": "v6.4.8", @@ -4940,6 +5009,199 @@ ], "time": "2024-04-18T09:32:20+00:00" }, + { + "name": "symfony/twig-bridge", + "version": "v6.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/9bcb26445b9d4ef1087c389234bf33fb00e10ea6", + "reference": "9bcb26445b9d4ef1087c389234bf33fb00e10ea6", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/translation-contracts": "^2.5|^3", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.2.2", + "phpdocumentor/type-resolver": "<1.4.0", + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", + "symfony/http-kernel": "<6.4", + "symfony/mime": "<6.2", + "symfony/serializer": "<6.4", + "symfony/translation": "<5.4", + "symfony/workflow": "<5.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": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/security-acl": "^2.8|^3.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.4.3|^7.0.3", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^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/v6.4.9" + }, + "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-06-21T16:04:15+00:00" + }, + { + "name": "symfony/twig-bundle", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bundle.git", + "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "reference": "ef17bc8fc2cb2376b235cd1b98f0275a78c5ba65", + "shasum": "" + }, + "require": { + "composer-runtime-api": ">=2.1", + "php": ">=8.1", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", + "twig/twig": "^2.13|^3.0.4" + }, + "conflict": { + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" + }, + "require-dev": { + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^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/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, { "name": "symfony/uid", "version": "v6.4.8", @@ -5345,6 +5607,160 @@ ], "time": "2024-05-31T14:49:08+00:00" }, + { + "name": "twig/extra-bundle", + "version": "v3.11.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/twig-extra-bundle.git", + "reference": "bf8a304eac15838d7724fdf64c345bdefbb75f03" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/twig-extra-bundle/zipball/bf8a304eac15838d7724fdf64c345bdefbb75f03", + "reference": "bf8a304eac15838d7724fdf64c345bdefbb75f03", + "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.11.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-06-21T06:25:01+00:00" + }, + { + "name": "twig/twig", + "version": "v3.11.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "reference": "e80fb8ebba85c7341a97a9ebf825d7fd4b77708d", + "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", + "symfony/polyfill-php81": "^1.29" + }, + "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.11.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-08-08T16:15:16+00:00" + }, { "name": "webmozart/assert", "version": "1.11.0", diff --git a/config/bundles.php b/config/bundles.php index e05d2dc..37ad000 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -7,4 +7,6 @@ Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], ]; diff --git a/config/packages/twig.yaml b/config/packages/twig.yaml new file mode 100644 index 0000000..3f795d9 --- /dev/null +++ b/config/packages/twig.yaml @@ -0,0 +1,6 @@ +twig: + file_name_pattern: '*.twig' + +when@test: + twig: + strict_variables: true diff --git a/config/routes/nelmio_api_doc.yaml b/config/routes/nelmio_api_doc.yaml index 364b4af..f350dd6 100644 --- a/config/routes/nelmio_api_doc.yaml +++ b/config/routes/nelmio_api_doc.yaml @@ -6,7 +6,7 @@ app.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 } +app.swagger_ui: + path: /api/doc + methods: GET + defaults: { _controller: nelmio_api_doc.controller.swagger_ui } diff --git a/symfony.lock b/symfony.lock index 8b9cf17..856a821 100644 --- a/symfony.lock +++ b/symfony.lock @@ -157,6 +157,19 @@ "config/routes.yaml" ] }, + "symfony/twig-bundle": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877" + }, + "files": [ + "config/packages/twig.yaml", + "templates/base.html.twig" + ] + }, "symfony/uid": { "version": "6.4", "recipe": { @@ -180,5 +193,8 @@ "files": [ "config/packages/validator.yaml" ] + }, + "twig/extra-bundle": { + "version": "v3.11.0" } } diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 0000000..1069c14 --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,16 @@ + + + + + {% block title %}Welcome!{% endblock %} + + {% block stylesheets %} + {% endblock %} + + {% block javascripts %} + {% endblock %} + + + {% block body %}{% endblock %} + + From abd36b48afd71939e2436ad41d34a3b810eb9f49 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Sat, 24 Aug 2024 21:59:14 +0300 Subject: [PATCH 03/10] #2 explicitly set api methods --- src/Controller/Api/V1/OrderController.php | 2 +- src/Controller/Api/V1/PaymentController.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Controller/Api/V1/OrderController.php b/src/Controller/Api/V1/OrderController.php index 278aa0d..2157f38 100644 --- a/src/Controller/Api/V1/OrderController.php +++ b/src/Controller/Api/V1/OrderController.php @@ -79,7 +79,7 @@ public function index( ], status: Response::HTTP_CREATED); } - #[Route('/{uuid}/status', name: 'status', format: 'json')] + #[Route('/{uuid}/status', name: 'status', methods: 'GET', format: 'json')] public function getStatus(Order $order): JsonResponse { return $this->json([ diff --git a/src/Controller/Api/V1/PaymentController.php b/src/Controller/Api/V1/PaymentController.php index 15c7bd1..a3a515f 100644 --- a/src/Controller/Api/V1/PaymentController.php +++ b/src/Controller/Api/V1/PaymentController.php @@ -44,7 +44,7 @@ public function getAvailableGateways(): JsonResponse return $this->json($gatewayCollection); } - #[Route('/{order_uuid}/handler', name: 'callback_handler', format: 'json')] + #[Route('/{order_uuid}/handler', name: 'callback_handler', methods: 'POST', format: 'json')] public function paymentCallbackHandler( #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, Request $request, @@ -76,7 +76,7 @@ public function paymentCallbackHandler( return new Response(); } - #[Route('/{order_uuid}/status', name: 'status', format: 'json')] + #[Route('/{order_uuid}/status', name: 'status', methods: 'GET', format: 'json')] public function getPaymentStatus( #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, ): JsonResponse { From c1e88480cbacc655766011b2adb1941529a4a319 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Sat, 24 Aug 2024 22:00:37 +0300 Subject: [PATCH 04/10] #2 consistency of order uuid parameter --- src/Controller/Api/V1/OrderController.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Controller/Api/V1/OrderController.php b/src/Controller/Api/V1/OrderController.php index 2157f38..972f58f 100644 --- a/src/Controller/Api/V1/OrderController.php +++ b/src/Controller/Api/V1/OrderController.php @@ -14,6 +14,7 @@ use App\Payment\PaymentGatewayRegistryInterface; use App\Repository\OrderRepository; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Response; @@ -79,9 +80,10 @@ public function index( ], status: Response::HTTP_CREATED); } - #[Route('/{uuid}/status', name: 'status', methods: 'GET', format: 'json')] - public function getStatus(Order $order): JsonResponse - { + #[Route('/{order_uuid}/status', name: 'status', methods: 'GET', format: 'json')] + public function getStatus( + #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, + ): JsonResponse { return $this->json([ 'order_status' => $order->getStatus(), ]); From 84b3dc8451c79a1e6808fab8d3ec8d752c7f6f45 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Mon, 26 Aug 2024 21:29:12 +0300 Subject: [PATCH 05/10] #2 api description --- config/packages/nelmio_api_doc.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/packages/nelmio_api_doc.yaml b/config/packages/nelmio_api_doc.yaml index 4e68479..93d4d80 100644 --- a/config/packages/nelmio_api_doc.yaml +++ b/config/packages/nelmio_api_doc.yaml @@ -1,8 +1,8 @@ nelmio_api_doc: documentation: info: - title: My App - description: This is an awesome app! + title: Payment Proxy Service + description: A proxy providing an api for payment your site orders via various payment gateways. version: 1.0.0 areas: # to filter documented areas path_patterns: From 988100b2f0398b9d5cfafa71e9d38618cf2f4d69 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Mon, 26 Aug 2024 21:29:47 +0300 Subject: [PATCH 06/10] #2 payment api documentation --- src/Controller/Api/V1/PaymentController.php | 35 ++++++++++---- src/Dto/PaymentGatewayDto.php | 9 ++++ src/Dto/PaymentStatusDto.php | 51 +++++++++++++++++++++ 3 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 src/Dto/PaymentStatusDto.php diff --git a/src/Controller/Api/V1/PaymentController.php b/src/Controller/Api/V1/PaymentController.php index a3a515f..ffc4b3a 100644 --- a/src/Controller/Api/V1/PaymentController.php +++ b/src/Controller/Api/V1/PaymentController.php @@ -5,6 +5,7 @@ namespace App\Controller\Api\V1; use App\Dto\PaymentGatewayDto; +use App\Dto\PaymentStatusDto; use App\Entity\Order; use App\Entity\OrderStatus; use App\Event\AfterPaymentCallbackHandlerEvent; @@ -13,6 +14,8 @@ use App\Order\Workflow\OrderWorkflowFactory; use App\Payment\Common\GatewayInterface; use App\Payment\PaymentGatewayRegistry; +use Nelmio\ApiDocBundle\Annotation\Model; +use OpenApi\Attributes as OA; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -30,6 +33,12 @@ public function __construct( ) { } + #[OA\Get(description: 'Get the available payment gateways.')] + #[OA\Response( + response: 200, + description: 'A list of available payment gateways.', + content: new Model(type: PaymentGatewayDto::class), + )] #[Route('/gateways', name: 'gateways', methods: 'GET', format: 'json')] public function getAvailableGateways(): JsonResponse { @@ -44,6 +53,13 @@ public function getAvailableGateways(): JsonResponse return $this->json($gatewayCollection); } + #[OA\Post( + description: 'Payment callback handler invoked by the payment gateway.', + )] + #[OA\Response( + response: 200, + description: 'Successful handled.', + )] #[Route('/{order_uuid}/handler', name: 'callback_handler', methods: 'POST', format: 'json')] public function paymentCallbackHandler( #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, @@ -76,6 +92,16 @@ public function paymentCallbackHandler( return new Response(); } + #[OA\Get(description: 'Get a payment status of the provided order from the payment gateway.')] + #[OA\Response( + response: 200, + description: 'Order payment status.', + content: new Model(type: PaymentStatusDto::class), + )] + #[OA\Response( + response: 404, + description: 'Order not found.', + )] #[Route('/{order_uuid}/status', name: 'status', methods: 'GET', format: 'json')] public function getPaymentStatus( #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, @@ -89,13 +115,6 @@ public function getPaymentStatus( $event = $this->eventDispatcher->dispatch(new PaymentStatusEvent($order, $statusResponse)); $statusResponse = $event->paymentStatusResponse; - return $this->json([ - 'payment_gateway' => $paymentGateway->getId(), - 'success' => $statusResponse->isSuccessful(), - 'transaction_id' => $statusResponse->getTransactionId(), - 'message' => $statusResponse->getMessage(), - 'code' => $statusResponse->getCode(), - 'data' => $statusResponse, - ]); + return $this->json(PaymentStatusDto::makeFromResponse($paymentGateway->getId(), $statusResponse)); } } diff --git a/src/Dto/PaymentGatewayDto.php b/src/Dto/PaymentGatewayDto.php index dbaf65c..12507f4 100644 --- a/src/Dto/PaymentGatewayDto.php +++ b/src/Dto/PaymentGatewayDto.php @@ -6,12 +6,21 @@ use App\Payment\Common\GatewayInterface; use Symfony\Component\Serializer\Attribute\SerializedName; +use OpenApi\Attributes as OA; +#[OA\Schema( + description: 'Payment Gateway object.' +)] final readonly class PaymentGatewayDto { public function __construct( + #[OA\Property(description: 'Payment gateway id.')] public string $id, + + #[OA\Property(description: 'Payment gateway name.')] public string $name, + + #[OA\Property(description: 'When test mode is positive, no real payments done.')] #[SerializedName('test_mode')] public bool $isTestMode, ) { } diff --git a/src/Dto/PaymentStatusDto.php b/src/Dto/PaymentStatusDto.php new file mode 100644 index 0000000..79a98a6 --- /dev/null +++ b/src/Dto/PaymentStatusDto.php @@ -0,0 +1,51 @@ +isSuccessful(), + transactionId: $response->getTransactionId(), + message: $response->getMessage(), + code: $response->getCode(), + data: $response->getRawData(), + ); + } +} From 6ea8a6c52e671a66ffd4b17be5aebe967226f3ad Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Mon, 26 Aug 2024 22:18:35 +0300 Subject: [PATCH 07/10] #2 describe order end points --- src/Controller/Api/V1/OrderController.php | 44 +++++++++++++++++++++-- src/Dto/OrderDto.php | 9 +++++ 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/Controller/Api/V1/OrderController.php b/src/Controller/Api/V1/OrderController.php index 972f58f..cf1c905 100644 --- a/src/Controller/Api/V1/OrderController.php +++ b/src/Controller/Api/V1/OrderController.php @@ -14,6 +14,7 @@ use App\Payment\PaymentGatewayRegistryInterface; use App\Repository\OrderRepository; use Doctrine\ORM\EntityManagerInterface; +use OpenApi\Attributes as OA; use Symfony\Bridge\Doctrine\Attribute\MapEntity; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; @@ -26,8 +27,23 @@ #[Route('/api/v1/order', name: 'api_v1_order_')] class OrderController extends AbstractController { - #[Route('', name: 'create', methods: 'POST', format: 'json')] - public function index( + #[OA\Post( + description: 'Purchase order.', + )] + #[OA\Response( + response: 201, + description: 'Order is added to the service and waiting for payment gateway callback.', + )] + #[OA\Response( + response: 409, + description: 'Order with same order number and payment gateway has been already added.', + )] + #[OA\Response( + response: 400, + description: 'Specified payment gateway is not available.', + )] + #[Route('', name: 'purchase', methods: 'POST', format: 'json')] + public function purchaseOrder( #[MapRequestPayload] OrderDto $orderDto, OrderTotalAmountCalculator $orderAmountCalculator, EntityManagerInterface $entityManager, @@ -80,8 +96,30 @@ public function index( ], status: Response::HTTP_CREATED); } + #[OA\Get( + description: 'Get order status.', + )] + #[OA\Response( + response: 200, + description: 'Order status.', + content: new OA\JsonContent( + properties: [ + new OA\Property( + property: 'order_status', + description: 'Order status, one of "created", "payment_pending", "payment_received", "payment_failed"', + type: 'string', + example: '"payment_pending"', + ), + ], + type: 'object', + ), + )] + #[OA\Response( + response: 404, + description: 'Order not found.', + )] #[Route('/{order_uuid}/status', name: 'status', methods: 'GET', format: 'json')] - public function getStatus( + public function getOrderStatus( #[MapEntity(mapping: ['order_uuid' => 'uuid'])] Order $order, ): JsonResponse { return $this->json([ diff --git a/src/Dto/OrderDto.php b/src/Dto/OrderDto.php index 6cc9575..f92139d 100644 --- a/src/Dto/OrderDto.php +++ b/src/Dto/OrderDto.php @@ -4,30 +4,39 @@ namespace App\Dto; +use OpenApi\Attributes as OA; use Symfony\Component\Serializer\Attribute\SerializedName; use Symfony\Component\Validator\Constraints as Assert; +#[OA\Schema( + description: 'A data object used for creating an order.', +)] final readonly class OrderDto { /** * @param array $products */ public function __construct( + #[OA\Property(description: 'Order number.')] #[Assert\Length(min:2, max: 255)] #[SerializedName('order_num')] public string $orderNum, + #[OA\Property(description: 'Which payment gateway use for order purchase.')] #[Assert\Length(min: 2, max: 16)] #[SerializedName('payment_gateway')] public string $paymentGateway, + #[OA\Property(description: 'Description of the order.')] #[Assert\Length(min: 2, max: 255)] public string $description, + #[OA\Property(description: 'List of the order products.')] #[Assert\Count(min: 1, max: 100)] #[Assert\Valid] public array $products, + #[OA\Property(description: 'Webpage url to return a customer after filling a payment form.')] #[Assert\Url] #[Assert\Length(max: 1000)] #[SerializedName('return_url')] From 697f324acbb910be936420a89044f5fd976d6389 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Mon, 26 Aug 2024 22:19:56 +0300 Subject: [PATCH 08/10] #2 class properties code style --- src/Dto/PaymentGatewayDto.php | 3 ++- src/Dto/PaymentStatusDto.php | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Dto/PaymentGatewayDto.php b/src/Dto/PaymentGatewayDto.php index 12507f4..d1cfb7a 100644 --- a/src/Dto/PaymentGatewayDto.php +++ b/src/Dto/PaymentGatewayDto.php @@ -21,7 +21,8 @@ public function __construct( public string $name, #[OA\Property(description: 'When test mode is positive, no real payments done.')] - #[SerializedName('test_mode')] public bool $isTestMode, + #[SerializedName('test_mode')] + public bool $isTestMode, ) { } diff --git a/src/Dto/PaymentStatusDto.php b/src/Dto/PaymentStatusDto.php index 79a98a6..e78ab50 100644 --- a/src/Dto/PaymentStatusDto.php +++ b/src/Dto/PaymentStatusDto.php @@ -14,15 +14,17 @@ final readonly class PaymentStatusDto { public function __construct( - #[OA\Property(description: 'Payment gateway id.')] - #[SerializedName('payment_gateway')] public string $paymentGatewayId, + #[SerializedName('payment_gateway')] + public string $paymentGatewayId, #[OA\Property(description: 'Transaction result.')] - #[SerializedName('success')] public bool $isSuccess, + #[SerializedName('success')] + public bool $isSuccess, #[OA\Property(description: 'Transaction id from the payment gateway side.')] - #[SerializedName('transaction_id')] public string $transactionId, + #[SerializedName('transaction_id')] + public string $transactionId, #[OA\Property(description: 'An error message.')] public string $message, From e04fead4a87171bc95d3defe6f8be6b8dcf83ecb Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Tue, 27 Aug 2024 19:54:30 +0300 Subject: [PATCH 09/10] #2 fix parameter name --- src/Controller/Api/V1/OrderController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller/Api/V1/OrderController.php b/src/Controller/Api/V1/OrderController.php index cf1c905..938e796 100644 --- a/src/Controller/Api/V1/OrderController.php +++ b/src/Controller/Api/V1/OrderController.php @@ -90,7 +90,7 @@ public function purchaseOrder( return $this->json([ 'order' => $order->getUuid(), 'status_check' => $this->generateUrl('api_v1_order_status', [ - 'uuid' => $order->getUuid(), + 'order_uuid' => $order->getUuid(), ], referenceType: UrlGeneratorInterface::ABSOLUTE_URL), 'payment_redirect_url' => $paymentRedirectResponse->getRedirectUrl(), ], status: Response::HTTP_CREATED); From 993b2eef1906c39edc81811263229c7053a84c02 Mon Sep 17 00:00:00 2001 From: Oleksii Skorobogatko Date: Tue, 27 Aug 2024 20:15:41 +0300 Subject: [PATCH 10/10] #2 README api documentation section --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index c8f082a..e6e74af 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,9 @@ _TBD: make docker installation_ symfony serve -d ``` +## API documentation + Two end-points are available for getting the API documentation: + - `/api/doc` swagger ui. + - `/api/doc.json` + as above but in Json format + for consuming by [Postman](https://www.postman.com/product/what-is-postman/), for example.