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/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl index 21478d04b..d4cf98e6d 100644 --- a/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl +++ b/src/Module/Templates/DefaultApi/src/Controllers/OpenApi/OpenApiController.php.tpl @@ -8,23 +8,24 @@ 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: '3.x', + description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' +)] +#[OA\SecurityScheme( + securityScheme: 'bearer_token', + type: 'apiKey', + name: 'Authorization', + in: 'header' +)] abstract class OpenApiController { } - 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(); } 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); } - - - 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(); } - - - 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); } - - - - 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..233854f34 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: '3.x', + description: ' *Quantum Documentation: https://quantumphp.io/en/docs/v1/overview' +)] +#[OA\SecurityScheme( + securityScheme: 'bearer_token', + type: 'apiKey', + name: 'Authorization', + in: 'header' +)] abstract class OpenApiController { } - 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); } - - - diff --git a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php new file mode 100644 index 000000000..6cd2737c5 --- /dev/null +++ b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php @@ -0,0 +1,144 @@ +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"); + } + } + } + + /** + * @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