diff --git a/app/Http/Controllers/Api/V1/ApiController.php b/app/Http/Controllers/Api/V1/ApiController.php index 132798dfb..28c09badf 100644 --- a/app/Http/Controllers/Api/V1/ApiController.php +++ b/app/Http/Controllers/Api/V1/ApiController.php @@ -4,9 +4,7 @@ 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 55d1281a6..1309c105e 100644 --- a/app/Http/Controllers/Api/V1/LatestResult.php +++ b/app/Http/Controllers/Api/V1/LatestResult.php @@ -13,11 +13,27 @@ class LatestResult extends ApiController { #[OA\Get( path: '/api/v1/results/latest', - description: 'Get the latest result.', + summary: 'Fetch the single most recent result', + operationId: 'getLatestResult', + tags: ['Results'], responses: [ - new OA\Response(response: 200, description: 'OK'), - new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'No result found'), - ])] + new OA\Response( + response: Response::HTTP_OK, + description: 'OK', + content: new OA\JsonContent(ref: '#/components/schemas/Result') + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_NOT_FOUND, + description: 'No result found', + content: new OA\JsonContent(ref: '#/components/schemas/NotFoundError') + ), + ] + )] public function __invoke(Request $request) { $result = Result::query() diff --git a/app/Http/Controllers/Api/V1/ListResults.php b/app/Http/Controllers/Api/V1/ListResults.php index d0789df8a..d634496c1 100644 --- a/app/Http/Controllers/Api/V1/ListResults.php +++ b/app/Http/Controllers/Api/V1/ListResults.php @@ -16,11 +16,29 @@ class ListResults extends ApiController { #[OA\Get( path: '/api/v1/results', - description: 'List results.', + summary: 'List results', + operationId: 'listResults', + tags: ['Results'], responses: [ - new OA\Response(response: Response::HTTP_OK, description: 'OK'), - new OA\Response(response: Response::HTTP_UNPROCESSABLE_ENTITY, description: 'Unprocessable Entity'), - ])] + new OA\Response( + response: Response::HTTP_OK, + description: 'OK', + content: new OA\JsonContent( + ref: '#/components/schemas/ResultsCollection' + ) + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_UNPROCESSABLE_ENTITY, + description: 'Validation failed', + content: new OA\JsonContent(ref: '#/components/schemas/ValidationError') + ), + ] + )] public function __invoke(Request $request) { $validator = Validator::make($request->all(), [ diff --git a/app/Http/Controllers/Api/V1/ListSpeedtestServers.php b/app/Http/Controllers/Api/V1/ListSpeedtestServers.php index 12b9839aa..c67efa648 100644 --- a/app/Http/Controllers/Api/V1/ListSpeedtestServers.php +++ b/app/Http/Controllers/Api/V1/ListSpeedtestServers.php @@ -11,10 +11,30 @@ class ListSpeedtestServers extends ApiController { #[OA\Get( path: '/api/v1/ookla/list-servers', - description: 'Get a list of available Ookla speedtest servers.', + summary: 'List available Ookla speedtest servers', + operationId: 'listSpeedtestServers', + tags: ['Servers'], responses: [ - new OA\Response(response: Response::HTTP_OK, description: 'OK'), - new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), + new OA\Response( + response: Response::HTTP_OK, + description: 'OK', + content: new OA\JsonContent( + ref: '#/components/schemas/ServersCollection' + ) + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_FORBIDDEN, + description: 'Forbidden', + content: new OA\JsonContent( + ref: '#/components/schemas/ForbiddenError', + example: ['message' => 'You do not have permission to view speedtest servers.'] + ) + ), ] )] public function __invoke(Request $request) diff --git a/app/Http/Controllers/Api/V1/RunSpeedtest.php b/app/Http/Controllers/Api/V1/RunSpeedtest.php index ae4588e0d..cf219bf0b 100644 --- a/app/Http/Controllers/Api/V1/RunSpeedtest.php +++ b/app/Http/Controllers/Api/V1/RunSpeedtest.php @@ -13,20 +13,39 @@ class RunSpeedtest extends ApiController { #[OA\Post( path: '/api/v1/speedtests/run', - description: 'Run a new Ookla speedtest. Optionally provide a server_id.', + summary: 'Run a new Ookla speedtest', + operationId: 'runSpeedtest', + tags: ['Speedtests'], parameters: [ new OA\Parameter( name: 'server_id', in: 'query', + description: 'Optional Ookla speedtest server ID', required: false, - schema: new OA\Schema(type: 'integer'), - description: 'Optional Ookla speedtest server ID' + schema: new OA\Schema(type: 'integer') ), ], responses: [ - new OA\Response(response: Response::HTTP_CREATED, description: 'Created'), - new OA\Response(response: Response::HTTP_FORBIDDEN, description: 'Forbidden'), - new OA\Response(response: Response::HTTP_UNPROCESSABLE_ENTITY, description: 'Validation error'), + new OA\Response( + response: Response::HTTP_CREATED, + description: 'Created', + content: new OA\JsonContent(ref: '#/components/schemas/SpeedtestRun') + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_FORBIDDEN, + description: 'Forbidden', + content: new OA\JsonContent(ref: '#/components/schemas/ForbiddenError') + ), + new OA\Response( + response: Response::HTTP_UNPROCESSABLE_ENTITY, + description: 'Validation error', + content: new OA\JsonContent(ref: '#/components/schemas/ValidationError') + ), ] )] public function __invoke(Request $request) diff --git a/app/Http/Controllers/Api/V1/ShowResult.php b/app/Http/Controllers/Api/V1/ShowResult.php index 4b5f42998..d30a8452c 100644 --- a/app/Http/Controllers/Api/V1/ShowResult.php +++ b/app/Http/Controllers/Api/V1/ShowResult.php @@ -13,11 +13,36 @@ class ShowResult extends ApiController { #[OA\Get( path: '/api/v1/results/{id}', - description: 'Get result.', + summary: 'Fetch a single result by ID', + operationId: 'getResult', + tags: ['Results'], + parameters: [ + new OA\Parameter( + name: 'id', + in: 'path', + required: true, + schema: new OA\Schema(type: 'integer'), + description: 'The ID of the result' + ), + ], responses: [ - new OA\Response(response: 200, description: 'OK'), - new OA\Response(response: Response::HTTP_NOT_FOUND, description: 'Result not found'), - ])] + new OA\Response( + response: Response::HTTP_OK, + description: 'OK', + content: new OA\JsonContent(ref: '#/components/schemas/ResultResponse') + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_NOT_FOUND, + description: 'Result not found', + content: new OA\JsonContent(ref: '#/components/schemas/NotFoundError') + ), + ] + )] public function __invoke(Request $request, int $id) { $result = Result::findOr($id, function () { diff --git a/app/Http/Controllers/Api/V1/Stats.php b/app/Http/Controllers/Api/V1/Stats.php index dcf3691e3..fead80892 100644 --- a/app/Http/Controllers/Api/V1/Stats.php +++ b/app/Http/Controllers/Api/V1/Stats.php @@ -5,6 +5,9 @@ use App\Http\Resources\V1\StatResource; use App\Models\Result; use Illuminate\Http\Request; +use Illuminate\Http\Response; +use OpenApi\Attributes as OA; +use OpenApi\Attributes\Schema as OASchema; use Spatie\QueryBuilder\AllowedFilter; use Spatie\QueryBuilder\Enums\FilterOperator; use Spatie\QueryBuilder\QueryBuilder; @@ -14,6 +17,45 @@ class Stats extends ApiController /** * Handle the incoming request. */ + #[OA\Get( + path: '/api/v1/stats', + summary: 'Fetch aggregated Speedtest statistics', + operationId: 'getStats', + tags: ['Stats'], + parameters: [ + new OA\Parameter( + name: 'start_at', + in: 'query', + description: 'ISO‑8601 start datetime filter', + required: false, + schema: new OASchema(type: 'string', format: 'date-time') + ), + new OA\Parameter( + name: 'end_at', + in: 'query', + description: 'ISO‑8601 end datetime filter', + required: false, + schema: new OASchema(type: 'string', format: 'date-time') + ), + ], + responses: [ + new OA\Response( + response: Response::HTTP_OK, + description: 'OK', + content: new OA\JsonContent(ref: '#/components/schemas/Stats') + ), + new OA\Response( + response: Response::HTTP_UNAUTHORIZED, + description: 'Unauthenticated', + content: new OA\JsonContent(ref: '#/components/schemas/UnauthenticatedError') + ), + new OA\Response( + response: Response::HTTP_UNPROCESSABLE_ENTITY, + description: 'Validation failed', + content: new OA\JsonContent(ref: '#/components/schemas/ValidationError') + ), + ] + )] public function __invoke(Request $request) { $stats = QueryBuilder::for(Result::class) diff --git a/app/OpenApi/OpenApiDefinition.php b/app/OpenApi/OpenApiDefinition.php new file mode 100644 index 000000000..6b8276680 --- /dev/null +++ b/app/OpenApi/OpenApiDefinition.php @@ -0,0 +1,38 @@ + [ + '12345' => 'Fibernet (New York, 12345)', + ], + ], + ), + new OA\Property( + property: 'message', + type: 'string', + description: 'Response status message' + ), + ], + additionalProperties: false +)] +class ServersCollectionSchema {} diff --git a/app/OpenApi/Schemas/SpeedtestRunSchema.php b/app/OpenApi/Schemas/SpeedtestRunSchema.php new file mode 100644 index 000000000..0509802bd --- /dev/null +++ b/app/OpenApi/Schemas/SpeedtestRunSchema.php @@ -0,0 +1,50 @@ +