diff --git a/app/Http/Controllers/Api/V1/ApiController.php b/app/Http/Controllers/Api/V1/ApiController.php index 24205bd2e..00637c38b 100644 --- a/app/Http/Controllers/Api/V1/ApiController.php +++ b/app/Http/Controllers/Api/V1/ApiController.php @@ -4,7 +4,9 @@ use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Support\Facades\Log; +use OpenApi\Attributes as OA; +#[OA\Info(title: 'Speedtest Tracker API', version: '1.0.0')] abstract class ApiController { /** diff --git a/app/Http/Controllers/Api/V1/LatestResult.php b/app/Http/Controllers/Api/V1/LatestResult.php index f0b6a10c3..55d1281a6 100644 --- a/app/Http/Controllers/Api/V1/LatestResult.php +++ b/app/Http/Controllers/Api/V1/LatestResult.php @@ -6,19 +6,25 @@ use App\Models\Result; use Http\Discovery\Exception\NotFoundException; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use OpenApi\Attributes as OA; class LatestResult extends ApiController { - /** - * Handle the incoming request. - */ + #[OA\Get( + path: '/api/v1/results/latest', + description: 'Get the latest result.', + responses: [ + new OA\Response(response: 200, description: 'OK'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'No result found'), + ])] public function __invoke(Request $request) { $result = Result::query() ->latest() ->firstOr(function () { self::throw( - e: new NotFoundException('No results found.'), + e: new NotFoundException('No result found.'), code: 404, ); }); diff --git a/app/Http/Controllers/Api/V1/ListResults.php b/app/Http/Controllers/Api/V1/ListResults.php index f253558b4..7cc136b76 100644 --- a/app/Http/Controllers/Api/V1/ListResults.php +++ b/app/Http/Controllers/Api/V1/ListResults.php @@ -2,20 +2,25 @@ namespace App\Http\Controllers\Api\V1; -use App\Http\Controllers\Controller; use App\Http\Resources\V1\ResultResource; use App\Models\Result; use Illuminate\Http\Request; +use Illuminate\Http\Response; use Illuminate\Support\Facades\Validator; +use OpenApi\Attributes as OA; use Spatie\QueryBuilder\AllowedFilter; use Spatie\QueryBuilder\Enums\FilterOperator; use Spatie\QueryBuilder\QueryBuilder; -class ListResults extends Controller +class ListResults extends ApiController { - /** - * Handle the incoming request. - */ + #[OA\Get( + path: '/api/v1/results', + description: 'List results.', + responses: [ + new OA\Response(response: Response::HTTP_OK, description: 'OK'), + new OA\Response(response: Response::HTTP_UNPROCESSABLE_ENTITY, description: 'Unprocessable Entity'), + ])] public function __invoke(Request $request) { $validator = Validator::make($request->all(), [ diff --git a/app/Http/Controllers/Api/V1/ShowResult.php b/app/Http/Controllers/Api/V1/ShowResult.php index acbdef7b8..4b5f42998 100644 --- a/app/Http/Controllers/Api/V1/ShowResult.php +++ b/app/Http/Controllers/Api/V1/ShowResult.php @@ -6,12 +6,18 @@ use App\Models\Result; use Http\Discovery\Exception\NotFoundException; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use OpenApi\Attributes as OA; class ShowResult extends ApiController { - /** - * Handle the incoming request. - */ + #[OA\Get( + path: '/api/v1/results/{id}', + description: 'Get result.', + responses: [ + new OA\Response(response: 200, description: 'OK'), + new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Result not found'), + ])] public function __invoke(Request $request, int $id) { $result = Result::findOr($id, function () { diff --git a/composer.json b/composer.json index 5c736a300..01c55968b 100644 --- a/composer.json +++ b/composer.json @@ -32,11 +32,12 @@ "lorisleiva/laravel-actions": "^2.8.5", "maennchen/zipstream-php": "^2.4", "ryangjchandler/blade-tabler-icons": "^2.3", - "spatie/laravel-json-api-paginate": "^1.16", + "spatie/laravel-json-api-paginate": "^1.16.1", "spatie/laravel-query-builder": "^6.3", "spatie/laravel-settings": "^3.4", "spatie/laravel-webhook-server": "^3.8.2", - "timokoerber/laravel-one-time-operations": "^1.4.4" + "timokoerber/laravel-one-time-operations": "^1.4.4", + "zircote/swagger-php": "^5.0" }, "require-dev": { "fakerphp/faker": "^1.24.1", diff --git a/composer.lock b/composer.lock index 90a62334d..872898368 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "48b74e71713ac2c17903d719d4a7c5cd", + "content-hash": "937d98ac4b6d88d6a5878895669f2915", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -8906,6 +8906,78 @@ ], "time": "2024-11-08T15:48:14+00:00" }, + { + "name": "symfony/yaml", + "version": "v7.2.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v7.2.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-10-23T06:56:12+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.3.0", @@ -9240,6 +9312,92 @@ "source": "https://github.com/webmozarts/assert/tree/1.11.0" }, "time": "2022-06-03T18:03:27+00:00" + }, + { + "name": "zircote/swagger-php", + "version": "5.0.2", + "source": { + "type": "git", + "url": "https://github.com/zircote/swagger-php.git", + "reference": "c6956de52edb270da4df2630b938c9ac3e26e42f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/c6956de52edb270da4df2630b938c9ac3e26e42f", + "reference": "c6956de52edb270da4df2630b938c9ac3e26e42f", + "shasum": "" + }, + "require": { + "ext-json": "*", + "nikic/php-parser": "^4.19 || ^5.0", + "php": ">=7.4", + "psr/log": "^1.1 || ^2.0 || ^3.0", + "symfony/deprecation-contracts": "^2 || ^3", + "symfony/finder": "^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^5.0 || ^6.0 || ^7.0" + }, + "conflict": { + "symfony/process": ">=6, <6.4.14" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/annotations": "^2.0", + "friendsofphp/php-cs-fixer": "^3.62.0", + "phpstan/phpstan": "^1.6 || ^2.0", + "phpunit/phpunit": "^9.0", + "rector/rector": "^1.0 || ^2.0", + "vimeo/psalm": "^4.30 || ^5.0" + }, + "suggest": { + "doctrine/annotations": "^2.0" + }, + "bin": [ + "bin/openapi" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "OpenApi\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Robert Allen", + "email": "zircote@gmail.com" + }, + { + "name": "Bob Fanger", + "email": "bfanger@gmail.com", + "homepage": "https://bfanger.nl" + }, + { + "name": "Martin Rademacher", + "email": "mano@radebatz.net", + "homepage": "https://radebatz.net" + } + ], + "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations", + "homepage": "https://github.com/zircote/swagger-php/", + "keywords": [ + "api", + "json", + "rest", + "service discovery" + ], + "support": { + "issues": "https://github.com/zircote/swagger-php/issues", + "source": "https://github.com/zircote/swagger-php/tree/5.0.2" + }, + "time": "2025-01-09T19:42:31+00:00" } ], "packages-dev": [ @@ -11763,78 +11921,6 @@ ], "time": "2024-10-20T05:08:20+00:00" }, - { - "name": "symfony/yaml", - "version": "v7.2.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "099581e99f557e9f16b43c5916c26380b54abb22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22", - "reference": "099581e99f557e9f16b43c5916c26380b54abb22", - "shasum": "" - }, - "require": { - "php": ">=8.2", - "symfony/deprecation-contracts": "^2.5|^3.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "symfony/console": "<6.4" - }, - "require-dev": { - "symfony/console": "^6.4|^7.0" - }, - "bin": [ - "Resources/bin/yaml-lint" - ], - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Loads and dumps YAML files", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/yaml/tree/v7.2.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-10-23T06:56:12+00:00" - }, { "name": "theseer/tokenizer", "version": "1.2.3", diff --git a/openapi.json b/openapi.json new file mode 100644 index 000000000..f66c53cb2 --- /dev/null +++ b/openapi.json @@ -0,0 +1,51 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Speedtest Tracker API", + "version": "1.0.0" + }, + "paths": { + "/api/v1/results/latest": { + "get": { + "description": "Get the latest result.", + "operationId": "4b44d6935caeb91d3e687ab4bc176f62", + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "No result found" + } + } + } + }, + "/api/v1/results": { + "get": { + "description": "List results.", + "operationId": "32a45ba8e8e9a81955a178ae686273ce", + "responses": { + "200": { + "description": "OK" + }, + "422": { + "description": "Unprocessable Entity" + } + } + } + }, + "/api/v1/results/{id}": { + "get": { + "description": "Get result.", + "operationId": "bc182a4008f0ff94a6318893359f3adc", + "responses": { + "200": { + "description": "OK" + }, + "404": { + "description": "Result not found" + } + } + } + } + } +} \ No newline at end of file