From 15f64584e5168001f86a66d69cd101f76f11794e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:31:25 +0400 Subject: [PATCH 01/10] [#597] Test that the API module templates produce a valid OpenAPI spec The OpenAPI documentation in the DefaultApi and DemoApi templates had no test. The test renders the OpenAPI controllers of each template, generates the specification the way the openapi command does, and checks that it validates, has a title and the bearer_token security scheme, and describes every operation with a summary and responses. --- .../Module/Templates/OpenApiTemplatesTest.php | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 tests/Unit/Module/Templates/OpenApiTemplatesTest.php diff --git a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php new file mode 100644 index 000000000..7868aa01f --- /dev/null +++ b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php @@ -0,0 +1,130 @@ +autoloader !== null) { + spl_autoload_unregister($this->autoloader); + $this->autoloader = null; + } + + if ($this->directory !== '' && is_dir($this->directory)) { + array_map('unlink', glob($this->directory . '/*') ?: []); + rmdir($this->directory); + } + } + + /** + * @dataProvider apiTemplateProvider + */ + public function testApiTemplatesProduceAValidOpenApiSpecification(string $template): void + { + $this->assertTrue($this->generate($template)->validate()); + } + + /** + * @dataProvider apiTemplateProvider + */ + public function testApiTemplatesDescribeTheApiAndItsSecurity(string $template): void + { + $spec = $this->generateSpecification($template); + + $this->assertNotEmpty($spec['info']['title']); + $this->assertArrayHasKey('bearer_token', $spec['components']['securitySchemes']); + } + + /** + * @dataProvider apiTemplateProvider + */ + public function testApiTemplatesDescribeEveryOperationWithASummaryAndResponses(string $template): void + { + $spec = $this->generateSpecification($template); + + $this->assertNotEmpty($spec['paths']); + + foreach ($spec['paths'] as $path => $operations) { + foreach ($operations as $method => $operation) { + $this->assertNotEmpty($operation['summary'] ?? null, "$method $path has no summary"); + $this->assertNotEmpty($operation['responses'] ?? null, "$method $path has no responses"); + } + } + } + + /** + * @return array + */ + public function apiTemplateProvider(): array + { + return [ + 'default-api' => ['DefaultApi'], + 'demo-api' => ['DemoApi'], + ]; + } + + /** + * @return array + */ + private function generateSpecification(string $template): array + { + return json_decode($this->generate($template)->toJson(), true); + } + + /** + * Renders the OpenAPI controllers of a module template and generates the specification the way the + * openapi command does. swagger-php reads the classes by reflection, so they get an autoloader. + */ + private function generate(string $template): OpenApi + { + $moduleNamespace = 'Modules\\' . $template; + $namespace = $moduleNamespace . '\\Controllers\\OpenApi'; + + $this->directory = sys_get_temp_dir() . '/quantum-openapi-' . $template . '-' . uniqid(); + mkdir($this->directory, 0777, true); + + $templates = glob(dirname(__DIR__, 4) . '/src/Module/Templates/' . $template . '/src/Controllers/OpenApi/*.php.tpl') ?: []; + + $this->assertNotEmpty($templates); + + foreach ($templates as $file) { + $content = str_replace( + ['{{MODULE_NAMESPACE}}', '{{MODULE_NAME}}'], + [$moduleNamespace, 'api'], + (string) file_get_contents($file) + ); + + file_put_contents($this->directory . '/' . basename($file, '.tpl'), $content); + } + + $this->autoloader = function (string $class) use ($namespace): void { + if (str_starts_with($class, $namespace . '\\')) { + $file = $this->directory . '/' . substr($class, strlen($namespace) + 1) . '.php'; + + if (is_file($file)) { + require_once $file; + } + } + }; + + spl_autoload_register($this->autoloader); + + $openApi = (new Generator())->generate([$this->directory]); + + $this->assertNotNull($openApi); + + return $openApi; + } +} From 7809d705487ddff2891dbe809f7160e1a24c8301 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:34:54 +0400 Subject: [PATCH 02/10] [#597] Write the DefaultApi OpenApiController template as attributes The API info and the bearer_token security scheme are PHP attributes instead of docblock annotations. The generated OpenAPI specification is identical. --- .../OpenApi/OpenApiController.php.tpl | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl index 21478d04b..ced712824 100644 --- a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl +++ b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl @@ -8,21 +8,23 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; + /** * Class ApiController * @package Modules\Api - * @OA\Info( - * title="Quantum API documentation", - * version="2.9.0", - * description=" *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview" - * ), - * @OA\SecurityScheme( - * securityScheme="bearer_token", - * type="apiKey", - * name="Authorization", - * in="header" - * ) */ +#[OA\Info( + title: 'Quantum API documentation', + version: '2.9.0', + description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' +)] +#[OA\SecurityScheme( + securityScheme: 'bearer_token', + type: 'apiKey', + name: 'Authorization', + in: 'header' +)] abstract class OpenApiController { From 6b51f6efd2f08cdc9166c8e8f2e43fae003cb69e Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:38:17 +0400 Subject: [PATCH 03/10] [#597] Write the DefaultApi OpenApiMainController template as attributes The status operation is a PHP attribute with its nested response, JSON content and properties instead of a docblock annotation. The generated OpenAPI specification is identical. --- .../OpenApi/OpenApiMainController.php.tpl | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiMainController.php.tpl b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiMainController.php.tpl index 29322da85..1edebe925 100644 --- a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiMainController.php.tpl +++ b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiMainController.php.tpl @@ -8,6 +8,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; use Quantum\Http\Response; /** @@ -17,23 +18,25 @@ use Quantum\Http\Response; abstract class OpenApiMainController extends OpenApiController { - /** - * @OA\Get( - * path="/{{MODULE_NAME}}", - * tags={"{{MODULE_NAME}}"}, - * summary="Get status of {{MODULE_NAME}}", - * description="Returns status of {{MODULE_NAME}} module.", - * @OA\Response( - * response=200, - * description="Successful response", - * @OA\JsonContent( - * type="object", - * @OA\Property(property="status", type="string", example="success"), - * @OA\Property(property="message", type="string", example="{{MODULE_NAME}} module.") - * ) - * ) - * ) - */ + #[OA\Get( + path: '/{{MODULE_NAME}}', + tags: ['{{MODULE_NAME}}'], + summary: 'Get status of {{MODULE_NAME}}', + description: 'Returns status of {{MODULE_NAME}} module.', + responses: [ + new OA\Response( + response: 200, + description: 'Successful response', + content: new OA\JsonContent( + type: 'object', + properties: [ + new OA\Property(property: 'status', type: 'string', example: 'success'), + new OA\Property(property: 'message', type: 'string', example: '{{MODULE_NAME}} module.'), + ] + ) + ), + ] + )] abstract public function index(); } From 008f03041c4ba8ce35f389a552dfddb7bcbc281b Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:40:06 +0400 Subject: [PATCH 04/10] [#597] Write the DemoApi OpenApiController template as attributes The API info and the bearer_token security scheme are PHP attributes instead of docblock annotations. The generated OpenAPI specification is identical. --- .../OpenApi/OpenApiController.php.tpl | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl index c9473a6bc..d1d3faf2c 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl @@ -8,23 +8,24 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; + /** * Class OpenApiController * @package Modules\Api - * @OA\Info( - * title="Quantum API documentation", - * version="2.9.0", - * description=" *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview" - * ), - * @OA\SecurityScheme( - * securityScheme="bearer_token", - * type="apiKey", - * name="Authorization", - * in="header" - * ) */ +#[OA\Info( + title: 'Quantum API documentation', + version: '2.9.0', + description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' +)] +#[OA\SecurityScheme( + securityScheme: 'bearer_token', + type: 'apiKey', + name: 'Authorization', + in: 'header' +)] abstract class OpenApiController { } - From 94eb86502347861bf948cecd2e5760049c7817b5 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:41:59 +0400 Subject: [PATCH 05/10] [#597] Update the OpenAPI documentation version of the API templates to 3.x The API info of the DefaultApi and DemoApi templates still said version 2.9.0. --- .../src/Controllers/OpenApi/OpenApiController.php.tpl | 3 +-- .../DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl index ced712824..d4cf98e6d 100644 --- a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl +++ b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl @@ -16,7 +16,7 @@ use OpenApi\Attributes as OA; */ #[OA\Info( title: 'Quantum API documentation', - version: '2.9.0', + version: '3.x', description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' )] #[OA\SecurityScheme( @@ -29,4 +29,3 @@ abstract class OpenApiController { } - diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl index d1d3faf2c..233854f34 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiController.php.tpl @@ -16,7 +16,7 @@ use OpenApi\Attributes as OA; */ #[OA\Info( title: 'Quantum API documentation', - version: '2.9.0', + version: '3.x', description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' )] #[OA\SecurityScheme( From 86a9ab39c88bd06063e1d47b2e171a278c175aac Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:48:11 +0400 Subject: [PATCH 06/10] [#597] Write the DemoApi OpenApiCommentController template as attributes --- .../OpenApi/OpenApiCommentController.php.tpl | 139 ++++++++++-------- 1 file changed, 77 insertions(+), 62 deletions(-) diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiCommentController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiCommentController.php.tpl index a70c1147e..1c22b3c23 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiCommentController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiCommentController.php.tpl @@ -8,6 +8,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; use Quantum\Http\Request; /** @@ -18,73 +19,87 @@ abstract class OpenApiCommentController extends OpenApiController { /** * Create comment action - * @OA\Post( - * path="/api/comments/create/{uuid}", - * tags={"Comments"}, - * summary="Create comment action", - * operationId="createComment", - * security={{"bearer_token": {}}}, - * @OA\Parameter(name="uuid", description="Post UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"content"}, - * @OA\Property(property="content", type="string"), - * example={"content": "Great post"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "message": "Created successfully", - * "data": { - * "uuid": "40f0e8a0-bcd6-11ee-9c66-9f57d21b5b9f", - * "post_uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "user_uuid": "e31a9f20-bcd5-11ee-8fe4-a77a76ad48c2", - * "content": "Great post" - * } - * } - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/comments/create/{uuid}', + tags: ['Comments'], + summary: 'Create comment action', + operationId: 'createComment', + security: [['bearer_token' => []]], + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Post UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['content'], + properties: [new OA\Property(property: 'content', type: 'string')], + example: ['content' => 'Great post'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'message' => 'Created successfully', + 'data' => [ + 'uuid' => '40f0e8a0-bcd6-11ee-9c66-9f57d21b5b9f', + 'post_uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'user_uuid' => 'e31a9f20-bcd5-11ee-8fe4-a77a76ad48c2', + 'content' => 'Great post', + ], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function create(Request $request, ?string $lang, string $uuid); /** * Delete comment action - * @OA\Delete( - * path="/api/comments/delete/{uuid}", - * tags={"Comments"}, - * summary="Delete comment action", - * operationId="deleteComment", - * security={{"bearer_token": {}}}, - * @OA\Parameter(name="uuid", description="Comment UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Deleted successfully"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Delete( + path: '/api/comments/delete/{uuid}', + tags: ['Comments'], + summary: 'Delete comment action', + operationId: 'deleteComment', + security: [['bearer_token' => []]], + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Comment UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Deleted successfully']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function delete(?string $lang, string $uuid); } - - - - From 8dee9a1242730046269eaf4997cd0e3e0f5e3e20 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:49:14 +0400 Subject: [PATCH 07/10] [#597] Write the DemoApi OpenApiAccountController template as attributes --- .../OpenApi/OpenApiAccountController.php.tpl | 134 +++++++++--------- 1 file changed, 70 insertions(+), 64 deletions(-) diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAccountController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAccountController.php.tpl index 37182d826..926f47412 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAccountController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAccountController.php.tpl @@ -8,6 +8,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; use Quantum\Http\Request; /** @@ -18,75 +19,80 @@ abstract class OpenApiAccountController extends OpenApiController { /** * Update user info action - * @OA\Put( - * path="/api/account-settings/update", - * tags={"Account"}, - * summary="Update user info", - * operationId="updateAccount", - * security={{"bearer_token": {}}}, - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"firstname", "lastname"}, - * @OA\Property(property="firstname", type="string"), - * @OA\Property(property="lastname", type="string"), - * example={"firstname": "Jon", "lastname": "Smit"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Updated successfully"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Put( + path: '/api/account-settings/update', + tags: ['Account'], + summary: 'Update user info', + operationId: 'updateAccount', + security: [['bearer_token' => []]], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['firstname', 'lastname'], + properties: [ + new OA\Property(property: 'firstname', type: 'string'), + new OA\Property(property: 'lastname', type: 'string'), + ], + example: ['firstname' => 'Jon', 'lastname' => 'Smit'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Updated successfully']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function update(Request $request); /** * Update password action - * @OA\Put( - * path="/api/account-settings/update-password", - * tags={"Account"}, - * summary="Update password", - * operationId="updatePassword", - * security={{"bearer_token": {}}}, - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"current_password", "new_password", "confirm_password"}, - * @OA\Property(property="current_password", type="string"), - * @OA\Property(property="new_password", type="string"), - * @OA\Property(property="confirm_password", type="string"), - * example={"current_password": "oldPassword", "new_password": "newPassword", "confirm_password": "newPassword"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Updated successfully"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Put( + path: '/api/account-settings/update-password', + tags: ['Account'], + summary: 'Update password', + operationId: 'updatePassword', + security: [['bearer_token' => []]], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['current_password', 'new_password', 'confirm_password'], + properties: [ + new OA\Property(property: 'current_password', type: 'string'), + new OA\Property(property: 'new_password', type: 'string'), + new OA\Property(property: 'confirm_password', type: 'string'), + ], + example: [ + 'current_password' => 'oldPassword', + 'new_password' => 'newPassword', + 'confirm_password' => 'newPassword', + ] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Updated successfully']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function updatePassword(Request $request); } - - - From 84bf1ad37ac43946ba9f5b2cab8d1f087c44add7 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:50:41 +0400 Subject: [PATCH 08/10] [#597] Write the DemoApi OpenApiPostController template as attributes --- .../OpenApi/OpenApiPostController.php.tpl | 516 ++++++++++-------- 1 file changed, 286 insertions(+), 230 deletions(-) diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiPostController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiPostController.php.tpl index e2ca22dd1..0ece31812 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiPostController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiPostController.php.tpl @@ -8,6 +8,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; use Quantum\Http\Request; /** @@ -18,266 +19,321 @@ abstract class OpenApiPostController extends OpenApiController { /** * Get posts action - * @OA\Get( - * path="/api/posts", - * tags={"Posts"}, - * summary="Get posts action", - * operationId="posts", - * @OA\Parameter(name="per_page", in="query", required=false, @OA\Schema(type="integer", default=8)), - * @OA\Parameter(name="page", in="query", required=false, @OA\Schema(type="integer", default=1)), - * @OA\Parameter(name="q", in="query", required=false, @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "data": { - * { - * "uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "title": "Demo Post", - * "content": "

Post content

", - * "image": "5d8f.../post-image.jpg", - * "date": "2026/05/14 10:30", - * "author": "Jon Smit" - * } - * }, - * "pagination": { - * "total_records": 10, - * "current_page": 1, - * "next_page": 2, - * "prev_page": null - * } - * } - * ) - * ), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/posts', + tags: ['Posts'], + summary: 'Get posts action', + operationId: 'posts', + parameters: [ + new OA\Parameter( + name: 'per_page', + in: 'query', + required: false, + schema: new OA\Schema(type: 'integer', default: 8) + ), + new OA\Parameter( + name: 'page', + in: 'query', + required: false, + schema: new OA\Schema(type: 'integer', default: 1) + ), + new OA\Parameter(name: 'q', in: 'query', required: false, schema: new OA\Schema(type: 'string')), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'data' => [ + [ + 'uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'title' => 'Demo Post', + 'content' => '

Post content

', + 'image' => '5d8f.../post-image.jpg', + 'date' => '2026/05/14 10:30', + 'author' => 'Jon Smit', + ], + ], + 'pagination' => [ + 'total_records' => 10, + 'current_page' => 1, + 'next_page' => 2, + 'prev_page' => null, + ], + ] + ) + ), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function posts(Request $request); /** * Get post action - * @OA\Get( - * path="/api/post/{uuid}", - * tags={"Posts"}, - * summary="Get post action", - * operationId="post", - * @OA\Parameter(name="uuid", description="Post UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "data": { - * "uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "title": "Demo Post", - * "content": "

Post content

", - * "image": "5d8f.../post-image.jpg", - * "date": "2026/05/14 10:30", - * "author": "Jon Smit", - * "comments": { - * { - * "uuid": "40f0e8a0-bcd6-11ee-9c66-9f57d21b5b9f", - * "author": { - * "firstname": "Jane", - * "lastname": "Doe", - * "image": "e31a.../avatar.png" - * }, - * "content": "Great post", - * "date": "2026-05-14 10:35" - * } - * } - * } - * } - * ) - * ), - * @OA\Response(response=404, description="Not Found"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/post/{uuid}', + tags: ['Posts'], + summary: 'Get post action', + operationId: 'post', + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Post UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'data' => [ + 'uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'title' => 'Demo Post', + 'content' => '

Post content

', + 'image' => '5d8f.../post-image.jpg', + 'date' => '2026/05/14 10:30', + 'author' => 'Jon Smit', + 'comments' => [ + [ + 'uuid' => '40f0e8a0-bcd6-11ee-9c66-9f57d21b5b9f', + 'author' => [ + 'firstname' => 'Jane', + 'lastname' => 'Doe', + 'image' => 'e31a.../avatar.png', + ], + 'content' => 'Great post', + 'date' => '2026-05-14 10:35', + ], + ], + ], + ] + ) + ), + new OA\Response(response: 404, description: 'Not Found'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function post(?string $lang, string $uuid); /** * Get my posts action - * @OA\Get( - * path="/api/my-posts", - * tags={"Posts"}, - * summary="Get my posts action", - * operationId="myPosts", - * security={{"bearer_token": {}}}, - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "data": { - * { - * "uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "title": "My Post", - * "content": "

Post content

", - * "image": "5d8f.../post-image.jpg", - * "date": "2026/05/14 10:30", - * "author": "Jon Smit" - * } - * } - * } - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/my-posts', + tags: ['Posts'], + summary: 'Get my posts action', + operationId: 'myPosts', + security: [['bearer_token' => []]], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'data' => [ + [ + 'uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'title' => 'My Post', + 'content' => '

Post content

', + 'image' => '5d8f.../post-image.jpg', + 'date' => '2026/05/14 10:30', + 'author' => 'Jon Smit', + ], + ], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function myPosts(); /** * Create post action - * @OA\Post( - * path="/api/my-posts/create", - * tags={"Posts"}, - * summary="Create post action", - * operationId="create", - * security={{"bearer_token": {}}}, - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="multipart/form-data", - * @OA\Schema( - * type="object", - * required={"title", "content"}, - * @OA\Property(property="title", type="string"), - * @OA\Property(property="content", type="string"), - * @OA\Property(property="image", type="string", format="binary") - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "message": "Created successfully", - * "data": { - * "uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "title": "Created Post", - * "content": "

Post content

", - * "image": "5d8f.../post-image.jpg", - * "date": "2026/05/14 10:30", - * "author": "Jon Smit" - * } - * } - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/my-posts/create', + tags: ['Posts'], + summary: 'Create post action', + operationId: 'create', + security: [['bearer_token' => []]], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'multipart/form-data', + schema: new OA\Schema( + type: 'object', + required: ['title', 'content'], + properties: [ + new OA\Property(property: 'title', type: 'string'), + new OA\Property(property: 'content', type: 'string'), + new OA\Property(property: 'image', type: 'string', format: 'binary'), + ] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'message' => 'Created successfully', + 'data' => [ + 'uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'title' => 'Created Post', + 'content' => '

Post content

', + 'image' => '5d8f.../post-image.jpg', + 'date' => '2026/05/14 10:30', + 'author' => 'Jon Smit', + ], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function create(Request $request); /** * Amend post action - * @OA\Put( - * path="/api/my-posts/amend/{uuid}", - * tags={"Posts"}, - * summary="Amend post action", - * operationId="amend", - * security={{"bearer_token": {}}}, - * @OA\Parameter(name="uuid", description="Post UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="multipart/form-data", - * @OA\Schema( - * type="object", - * required={"title", "content"}, - * @OA\Property(property="title", type="string"), - * @OA\Property(property="content", type="string"), - * @OA\Property(property="image", type="string", format="binary") - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "message": "Updated successfully", - * "data": { - * "uuid": "4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af", - * "title": "Updated Post", - * "content": "

Updated content

", - * "image": "5d8f.../post-image.jpg", - * "date": "2026/05/14 11:00", - * "author": "Jon Smit" - * } - * } - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Put( + path: '/api/my-posts/amend/{uuid}', + tags: ['Posts'], + summary: 'Amend post action', + operationId: 'amend', + security: [['bearer_token' => []]], + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Post UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'multipart/form-data', + schema: new OA\Schema( + type: 'object', + required: ['title', 'content'], + properties: [ + new OA\Property(property: 'title', type: 'string'), + new OA\Property(property: 'content', type: 'string'), + new OA\Property(property: 'image', type: 'string', format: 'binary'), + ] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'message' => 'Updated successfully', + 'data' => [ + 'uuid' => '4e9b8f47-bcd5-11ee-a0f2-fb642f7f26af', + 'title' => 'Updated Post', + 'content' => '

Updated content

', + 'image' => '5d8f.../post-image.jpg', + 'date' => '2026/05/14 11:00', + 'author' => 'Jon Smit', + ], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function amend(Request $request, ?string $lang, string $uuid); /** * Delete post action - * @OA\Delete( - * path="/api/my-posts/delete/{uuid}", - * tags={"Posts"}, - * summary="Delete post action", - * operationId="delete", - * security={{"bearer_token": {}}}, - * @OA\Parameter(name="uuid", description="Post UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Deleted successfully"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Delete( + path: '/api/my-posts/delete/{uuid}', + tags: ['Posts'], + summary: 'Delete post action', + operationId: 'delete', + security: [['bearer_token' => []]], + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Post UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Deleted successfully']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function delete(?string $lang, string $uuid); /** * Delete post image action - * @OA\Delete( - * path="/api/my-posts/delete-image/{uuid}", - * tags={"Posts"}, - * summary="Delete post image action", - * operationId="deleteImage", - * security={{"bearer_token": {}}}, - * @OA\Parameter(name="uuid", description="Post UUID", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Deleted successfully"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Delete( + path: '/api/my-posts/delete-image/{uuid}', + tags: ['Posts'], + summary: 'Delete post image action', + operationId: 'deleteImage', + security: [['bearer_token' => []]], + parameters: [ + new OA\Parameter( + name: 'uuid', + description: 'Post UUID', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Deleted successfully']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function deleteImage(?string $lang, string $uuid); } - - - From 95078bf9b255757ab917b86913ac0ad80e2954f2 Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:51:12 +0400 Subject: [PATCH 09/10] [#597] Write the DemoApi OpenApiAuthController template as attributes --- .../OpenApi/OpenApiAuthController.php.tpl | 525 ++++++++++-------- 1 file changed, 285 insertions(+), 240 deletions(-) diff --git a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAuthController.php.tpl b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAuthController.php.tpl index ffc99fd11..7e321989d 100644 --- a/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAuthController.php.tpl +++ b/src/Module/Templates/DemoApi/src/Controllers/OpenApi/OpenApiAuthController.php.tpl @@ -8,6 +8,7 @@ namespace {{MODULE_NAMESPACE}}\Controllers\OpenApi; +use OpenApi\Attributes as OA; use Quantum\Http\Request; /** @@ -18,286 +19,330 @@ abstract class OpenApiAuthController extends OpenApiController { /** * Sign in action - * @OA\Post( - * path="/api/signin", - * tags={"Authentication"}, - * summary="Sign in action", - * operationId="userSignIn", - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"email", "password"}, - * @OA\Property(property="email", type="string"), - * @OA\Property(property="password", type="string"), - * example={"email": "mail@example.com", "password": "password"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * oneOf={ - * @OA\Schema( - * example={ - * "status": "success", - * "tokens": { - * "access_token": "base64-jwt-token", - * "refresh_token": "refresh-token" - * } - * } - * ), - * @OA\Schema(example={"status": "success", "code": "otp_token"}) - * } - * ) - * ), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/signin', + tags: ['Authentication'], + summary: 'Sign in action', + operationId: 'userSignIn', + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['email', 'password'], + properties: [ + new OA\Property(property: 'email', type: 'string'), + new OA\Property(property: 'password', type: 'string'), + ], + example: ['email' => 'mail@example.com', 'password' => 'password'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + oneOf: [ + new OA\Schema( + example: [ + 'status' => 'success', + 'tokens' => ['access_token' => 'base64-jwt-token', 'refresh_token' => 'refresh-token'], + ] + ), + new OA\Schema(example: ['status' => 'success', 'code' => 'otp_token']), + ] + ) + ), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function signin(Request $request); /** * Gets the logged-in user data - * @OA\Get( - * path="/api/me", - * tags={"User"}, - * summary="Gets the logged-in user data", - * operationId="me", - * security={{"bearer_token": {}}}, - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "data": {"firstname": "Jon", "lastname": "Smit", "email": "mail@example.com"}} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/me', + tags: ['User'], + summary: 'Gets the logged-in user data', + operationId: 'me', + security: [['bearer_token' => []]], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'data' => ['firstname' => 'Jon', 'lastname' => 'Smit', 'email' => 'mail@example.com'], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function me(); /** * Sign out action - * @OA\Get( - * path="/api/signout", - * tags={"Authentication"}, - * summary="Sign out action", - * operationId="signout", - * @OA\Parameter(name="refresh_token", description="Refresh token", required=true, in="header", @OA\Schema(type="string")), - * security={{"bearer_token": {}}}, - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "tokens": { - * "access_token": "base64-jwt-token", - * "refresh_token": "refresh-token" - * } - * } - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/signout', + tags: ['Authentication'], + summary: 'Sign out action', + operationId: 'signout', + parameters: [ + new OA\Parameter( + name: 'refresh_token', + description: 'Refresh token', + required: true, + in: 'header', + schema: new OA\Schema(type: 'string') + ), + ], + security: [['bearer_token' => []]], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'tokens' => ['access_token' => 'base64-jwt-token', 'refresh_token' => 'refresh-token'], + ] + ) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function signout(); /** * Sign up action - * @OA\Post( - * path="/api/signup", - * tags={"Authentication"}, - * summary="Sign up action", - * operationId="signUpApi", - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"email", "password", "firstname", "lastname"}, - * @OA\Property(property="email", type="string"), - * @OA\Property(property="password", type="string"), - * @OA\Property(property="firstname", type="string"), - * @OA\Property(property="lastname", type="string"), - * example={"email": "mail@example.com", "password": "password", "firstname": "Jon", "lastname": "Smit"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Successfully signed up"} - * ) - * ), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/signup', + tags: ['Authentication'], + summary: 'Sign up action', + operationId: 'signUpApi', + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['email', 'password', 'firstname', 'lastname'], + properties: [ + new OA\Property(property: 'email', type: 'string'), + new OA\Property(property: 'password', type: 'string'), + new OA\Property(property: 'firstname', type: 'string'), + new OA\Property(property: 'lastname', type: 'string'), + ], + example: [ + 'email' => 'mail@example.com', + 'password' => 'password', + 'firstname' => 'Jon', + 'lastname' => 'Smit', + ] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Successfully signed up']) + ), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function signup(Request $request); /** * Activate action - * @OA\Get( - * path="/api/activate/{token}", - * tags={"Authentication"}, - * summary="Activate action", - * operationId="activateProfile", - * @OA\Parameter(name="token", description="Activate token", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Account activated"} - * ) - * ), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/activate/{token}', + tags: ['Authentication'], + summary: 'Activate action', + operationId: 'activateProfile', + parameters: [ + new OA\Parameter( + name: 'token', + description: 'Activate token', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Account activated']) + ), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function activate(Request $request); /** * Forget action - * @OA\Post( - * path="/api/forget", - * tags={"Authentication"}, - * summary="Forget action", - * operationId="forgetPassword", - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"email"}, - * @OA\Property(property="email", type="string"), - * example={"email": "mail@example.com"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "message": "Check your email"} - * ) - * ), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/forget', + tags: ['Authentication'], + summary: 'Forget action', + operationId: 'forgetPassword', + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['email'], + properties: [new OA\Property(property: 'email', type: 'string')], + example: ['email' => 'mail@example.com'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'message' => 'Check your email']) + ), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function forget(Request $request); /** * Reset action - * @OA\Post( - * path="/api/reset/{token}", - * tags={"Authentication"}, - * summary="Reset action", - * operationId="resetPassword", - * @OA\Parameter(name="token", description="Reset token", required=true, in="path", @OA\Schema(type="string")), - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"password", "repeat_password"}, - * @OA\Property(property="password", type="string"), - * @OA\Property(property="repeat_password", type="string"), - * example={"password": "password", "repeat_password": "password"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={ - * "status": "success", - * "tokens": { - * "access_token": "base64-jwt-token", - * "refresh_token": "refresh-token" - * } - * } - * ) - * ), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/reset/{token}', + tags: ['Authentication'], + summary: 'Reset action', + operationId: 'resetPassword', + parameters: [ + new OA\Parameter( + name: 'token', + description: 'Reset token', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['password', 'repeat_password'], + properties: [ + new OA\Property(property: 'password', type: 'string'), + new OA\Property(property: 'repeat_password', type: 'string'), + ], + example: ['password' => 'password', 'repeat_password' => 'password'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent( + example: [ + 'status' => 'success', + 'tokens' => ['access_token' => 'base64-jwt-token', 'refresh_token' => 'refresh-token'], + ] + ) + ), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function reset(Request $request); /** * Verify action - * @OA\Post( - * path="/api/verify", - * tags={"Authentication"}, - * summary="Verify action", - * operationId="accountVerify", - * @OA\RequestBody( - * required=true, - * @OA\MediaType( - * mediaType="application/json", - * @OA\Schema( - * required={"otp", "code"}, - * @OA\Property(property="otp", type="integer"), - * @OA\Property(property="code", type="string"), - * example={"otp": 123456, "code": "otp_token"} - * ) - * ) - * ), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Post( + path: '/api/verify', + tags: ['Authentication'], + summary: 'Verify action', + operationId: 'accountVerify', + requestBody: new OA\RequestBody( + required: true, + content: new OA\MediaType( + mediaType: 'application/json', + schema: new OA\Schema( + required: ['otp', 'code'], + properties: [ + new OA\Property(property: 'otp', type: 'integer'), + new OA\Property(property: 'code', type: 'string'), + ], + example: ['otp' => 123456, 'code' => 'otp_token'] + ) + ) + ), + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function verify(Request $request); /** * Resend action - * @OA\Get( - * path="/api/resend/{code}", - * tags={"Authentication"}, - * summary="Resend action", - * operationId="resendOTP", - * @OA\Parameter(name="code", description="OTP code", required=true, in="path", @OA\Schema(type="string")), - * @OA\Response( - * response=200, - * description="Success", - * @OA\JsonContent( - * example={"status": "success", "code": "otp_token"} - * ) - * ), - * @OA\Response(response=401, description="Unauthorized Request"), - * @OA\Response(response=422, description="Unprocessable Entity"), - * @OA\Response(response=429, description="Too Many Requests"), - * @OA\Response(response=500, description="Internal Server Error") - * ) */ + #[OA\Get( + path: '/api/resend/{code}', + tags: ['Authentication'], + summary: 'Resend action', + operationId: 'resendOTP', + parameters: [ + new OA\Parameter( + name: 'code', + description: 'OTP code', + required: true, + in: 'path', + schema: new OA\Schema(type: 'string') + ), + ], + responses: [ + new OA\Response( + response: 200, + description: 'Success', + content: new OA\JsonContent(example: ['status' => 'success', 'code' => 'otp_token']) + ), + new OA\Response(response: 401, description: 'Unauthorized Request'), + new OA\Response(response: 422, description: 'Unprocessable Entity'), + new OA\Response(response: 429, description: 'Too Many Requests'), + new OA\Response(response: 500, description: 'Internal Server Error'), + ] + )] abstract public function resend(); } - - - From e8ddf56d5e4abb6f7d56bc6bc89277639af96a1f Mon Sep 17 00:00:00 2001 From: Arman <407448+armanist@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:57:27 +0400 Subject: [PATCH 10/10] [#597] Remove doctrine/annotations and test that the API templates use attributes only --- composer.json | 1 - .../Unit/Module/Templates/OpenApiTemplatesTest.php | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 37ddf0f99..12595cb65 100644 --- a/composer.json +++ b/composer.json @@ -47,7 +47,6 @@ "zircote/swagger-php": "^5.8", "psr/simple-cache": "^1.0", "vaibhavpandeyvpz/phemail": "^1.1", - "doctrine/annotations": "^2.0", "voku/html-min": "^5.0", "league/commonmark": "^2.0", "ezyang/htmlpurifier": "^4.18", diff --git a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php index 7868aa01f..6cd2737c5 100644 --- a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php +++ b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php @@ -64,6 +64,20 @@ public function testApiTemplatesDescribeEveryOperationWithASummaryAndResponses(s } } + /** + * @dataProvider apiTemplateProvider + */ + public function testApiTemplatesDescribeTheApiWithAttributesOnly(string $template): void + { + $templates = glob(dirname(__DIR__, 4) . '/src/Module/Templates/' . $template . '/src/Controllers/OpenApi/*.php.tpl') ?: []; + + $this->assertNotEmpty($templates); + + foreach ($templates as $file) { + $this->assertStringNotContainsString('@OA\\', (string) file_get_contents($file), basename($file)); + } + } + /** * @return array */