Provide a dynamic openapi.yml manifest, fix OpenAPI markup on several sections; update Swagger UI.

This commit is contained in:
Buster "Silver Eagle" Neece 2018-12-24 02:47:45 -06:00
parent e21a23ef35
commit 10970517ac
22 changed files with 264 additions and 120 deletions

8
composer.lock generated
View File

@ -95,12 +95,12 @@
"source": {
"type": "git",
"url": "https://github.com/AzuraCast/azuracore.git",
"reference": "b1254020bdb71a266c38747466bbd7ce5b992553"
"reference": "d3cb9c69d0a8bd6f409566eab45ae2620f3d8449"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AzuraCast/azuracore/zipball/b1254020bdb71a266c38747466bbd7ce5b992553",
"reference": "b1254020bdb71a266c38747466bbd7ce5b992553",
"url": "https://api.github.com/repos/AzuraCast/azuracore/zipball/d3cb9c69d0a8bd6f409566eab45ae2620f3d8449",
"reference": "d3cb9c69d0a8bd6f409566eab45ae2620f3d8449",
"shasum": ""
},
"require": {
@ -145,7 +145,7 @@
}
],
"description": "A lightweight core application framework.",
"time": "2018-12-22T03:04:57+00:00"
"time": "2018-12-23T18:57:31+00:00"
},
{
"name": "azuracast/azuraforms",

View File

@ -155,6 +155,9 @@ return function(App $app)
$this->get('', Controller\Api\IndexController::class.':indexAction')
->setName('api:index:index');
$this->get('/openapi.yml', Controller\Api\OpenApiController::class)
->setName('api:openapi');
$this->get('/status', Controller\Api\IndexController::class.':statusAction')
->setName('api:index:status');
@ -198,31 +201,19 @@ return function(App $app)
$this->group('/admin', function() {
/** @var App $this */
$crud_areas = [
Controller\Api\Admin\UsersController::class => [
'collection' => 'users',
'item' => 'user',
'permission' => Acl::GLOBAL_USERS,
],
];
$this->group('', function() {
/** @var App $this */
$this->get('/users', Controller\Api\Admin\UsersController::class.':listAction')
->setName('api:admin:users');
$this->post('/users', Controller\Api\Admin\UsersController::class.':createAction');
foreach($crud_areas as $controller => $route_info) {
$this->group('', function() use ($controller, $route_info) {
/** @var App $this */
$this->get('/user/{id}', Controller\Api\Admin\UsersController::class.':getAction')
->setName('api:admin:user');
$this->put('/user/{id}', Controller\Api\Admin\UsersController::class.':editAction');
$this->delete('/user/{id}', Controller\Api\Admin\UsersController::class.':deleteAction');
})->add([Middleware\Permissions::class, Acl::GLOBAL_USERS]);
$this->get('/'.$route_info['collection'], $controller.':listAction')
->setName('api:admin:'.$route_info['collection']);
$this->post('/'.$route_info['collection'], $controller.':createAction');
$this->get('/'.$route_info['item'].'/{id}', $controller.':getAction')
->setName('api:admin:'.$route_info['item']);
$this->put('/'.$route_info['item'].'/{id}', $controller.':editAction');
$this->delete($route_info['item'].'/{id}', $controller.':deleteAction');
})->add([Middleware\Permissions::class, $route_info['permission']]);
}
});
$this->group('/station/{station}', function () {
@ -240,15 +231,13 @@ return function(App $app)
$this->group('/queue', function() {
/** @var App $this */
$this->get('/queue', Controller\Api\Stations\QueueController::class.':listAction')
$this->get('', Controller\Api\Stations\QueueController::class.':listAction')
->setName('api:stations:queue');
$this->get('/queue/{id}', Controller\Api\Stations\QueueController::class.':getAction')
$this->get('/{id}', Controller\Api\Stations\QueueController::class.':getAction')
->setName('api:stations:queue:record');
$this->delete('/queue/{id}', Controller\Api\Stations\QueueController::class.':deleteAction');
$this->delete('/{id}', Controller\Api\Stations\QueueController::class.':deleteAction');
})->add([Middleware\Permissions::class, Acl::STATION_BROADCASTING, true]);
$this->get('/requests', Controller\Api\RequestsController::class.':listAction')

View File

@ -32,7 +32,6 @@ class App extends \Azura\App
define('APP_TESTING_MODE', (isset($settings[Settings::APP_ENV]) && Settings::ENV_TESTING === $settings[Settings::APP_ENV]));
// Constants used in annotations
define('AZURACAST_VERSION', Version::FALLBACK_VERSION);
define('SAMPLE_TIMESTAMP', rand(time() - 86400, time() + 86400));
// Register the plugins engine.

View File

@ -28,6 +28,10 @@ class GenerateApiDocs extends CommandAbstract
/** @var Settings $settings */
$settings = $this->get(Settings::class);
define('AZURACAST_API_URL', 'https://demo.azuracast.com/api');
define('AZURACAST_API_NAME', 'AzuraCast Public Demo Server');
define('AZURACAST_VERSION', Version::FALLBACK_VERSION);
$oa = \OpenApi\scan([
$settings[Settings::BASE_DIR] . '/util/openapi.php',
$settings[Settings::BASE_DIR] . '/src/Entity',

View File

@ -13,23 +13,78 @@ class UsersController extends AbstractGenericCrudController
protected $resourceRouteName = 'api:admin:user';
/**
* @OA\Get(path="/station/{station_id}/listeners",
* tags={"Stations: Listeners"},
* description="Return detailed information about current listeners.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* type="array",
* @OA\Items(ref="#/components/schemas/Api_Listener")
* )
* @OA\Get(path="/admin/users",
* tags={"Administration: Users"},
* description="List all current users in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/User"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={
* {"api_key"}
* },
* security={{"api_key": \App\Acl::GLOBAL_USERS}},
* )
*
* @OA\Post(path="/admin/users",
* tags={"Administration: Users"},
* description="Create a new user.",
* @OA\RequestBody(ref="#/components/schemas/User"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/User")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": \App\Acl::GLOBAL_USERS}},
* )
*
* @OA\Get(path="/admin/user/{id}",
* tags={"Administration: Users"},
* description="Retrieve details for a single current user.",
* @OA\Parameter(
* name="id",
* in="path",
* description="User ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/User")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": \App\Acl::GLOBAL_USERS}},
* )
*
* @OA\Put(path="/admin/user/{id}",
* tags={"Administration: Users"},
* description="Update details of a single user.",
* @OA\RequestBody(ref="#/components/schemas/User"),
* @OA\Parameter(
* name="id",
* in="path",
* description="User ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/API_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": \App\Acl::GLOBAL_USERS}},
* )
*
* @OA\Delete(path="/admin/user/{id}",
* tags={"Administration: Users"},
* description="Delete a single user.",
* @OA\Parameter(
* name="id",
* in="path",
* description="User ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/API_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": \App\Acl::GLOBAL_USERS}},
* )
*/
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Controller\Api;
use App\Http\Request;
use App\Http\Response;
use App\Version;
use Azura\Settings;
use Psr\Http\Message\ResponseInterface;
class OpenApiController
{
/** @var Settings */
protected $settings;
/** @var Version */
protected $version;
/**
* @param Settings $settings
* @param Version $version
*
* @see \App\Provider\ApiProvider
*/
public function __construct(Settings $settings, Version $version)
{
$this->settings = $settings;
$this->version = $version;
}
public function __invoke(Request $request, Response $response): ResponseInterface
{
$router = $request->getRouter();
$api_base_url = (string)$router->fromHere(null, [], [], true);
$api_base_url = str_replace('/openapi.yml', '', $api_base_url);
define('AZURACAST_API_URL', $api_base_url);
define('AZURACAST_API_NAME', 'This AzuraCast Installation');
define('AZURACAST_VERSION', $this->version->getVersion());
$oa = \OpenApi\scan([
$this->settings[Settings::BASE_DIR] . '/util/openapi.php',
$this->settings[Settings::BASE_DIR] . '/src/Entity',
$this->settings[Settings::BASE_DIR] . '/src/Controller/Api',
], [
'exclude' => [
'bootstrap',
'locale',
'templates'
],
]);
$yaml = $oa->toYaml();
return $response
->withHeader('Content-Type', 'text/x-yaml')
->write($yaml);
}
}

View File

@ -32,11 +32,8 @@ class IndexController
* tags={"Stations: General"},
* description="Returns a list of stations.",
* parameters={},
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* type="array",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_Station")
* )
* )
@ -70,12 +67,8 @@ class IndexController
* tags={"Stations: General"},
* description="Return information about a single station.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* ref="#/components/schemas/Api_Station"
* )
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Station")
* ),
* @OA\Response(response=404, description="Station not found")
* )

View File

@ -41,14 +41,8 @@ class MediaController
* type="string"
* )
* ),
* @OA\Response(
* response=200,
* description="The requested album artwork"
* ),
* @OA\Response(
* response=404,
* description="Image not found; generic filler image."
* )
* @OA\Response(response=200, description="The requested album artwork"),
* @OA\Response(response=404, description="Image not found; generic filler image.")
* )
*/
public function artAction(Request $request, Response $response, $station_id, $media_id): ResponseInterface

View File

@ -32,20 +32,17 @@ class QueueController extends AbstractStationCrudController
/**
* @OA\Get(path="/station/{station_id}/queue",
* tags={"Stations: Listeners"},
* tags={"Stations: Queue"},
* description="Return information about the upcoming song playback queue.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* type="array",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_QueuedSong")
* )
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key"}},
* security={{"api_key": \App\Acl::STATION_REPORTS}},
* )
*
* @inheritdoc
@ -83,20 +80,42 @@ class QueueController extends AbstractStationCrudController
}
/**
* @inheritdoc
* @OA\Get(path="/station/{station_id}/queue/{id}",
* tags={"Stations: Queue"},
* description="Retrieve details of a single queued item.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Queue Item ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_QueuedSong")
* ),
* @OA\Response(response=404, description="Station or Queue ID not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": App\Acl::STATION_REPORTS}},
* )
*
* @OA\Delete(path="/station/{station_id}/queue/{id}",
* tags={"Stations: Queue"},
* description="Retrieve details of a single queued item.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Queue Item ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/API_Status")
* ),
* @OA\Response(response=404, description="Station or Queue ID not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": App\Acl::STATION_REPORTS}},
* )
*/
public function getAction(Request $request, Response $response, $station_id, $record_id): ResponseInterface
{
return parent::getAction($request, $response, $station_id, $record_id);
}
/**
* @inheritdoc
*/
public function deleteAction(Request $request, Response $response, $station_id, $record_id): ResponseInterface
{
return parent::deleteAction($request, $response, $station_id, $record_id);
}
/**
* @inheritdoc

View File

@ -36,6 +36,13 @@ class ApiProvider implements ServiceProviderInterface
);
};
$di[Api\OpenApiController::class] = function($di) {
return new Api\OpenApiController(
$di[\Azura\Settings::class],
$di[\App\Version::class]
);
};
$di[Api\Stations\QueueController::class] = function($di) {
return new Api\Stations\QueueController(
$di[\Doctrine\ORM\EntityManager::class],

View File

@ -11,8 +11,8 @@
* )
*
* @OA\Server(
* description="AzuraCast Demo API",
* url="https://demo.azuracast.com/api"
* description=AZURACAST_API_NAME,
* url=AZURACAST_API_URL
* )
*
* @OA\ExternalDocumentation(
@ -41,10 +41,17 @@
* name="Now Playing",
* description="Endpoints that provide full summaries of the current state of stations.",
* )
* @OA\Tag(name="Miscellaneous")
*
* @OA\Tag(name="Stations: General")
* @OA\Tag(name="Stations: Song Requests")
* @OA\Tag(name="Stations: Listeners")
* @OA\Tag(name="Stations: Media")
* @OA\Tag(name="Stations: Queue")
* @OA\Tag(name="Stations: Service Control")
*
* @OA\Tag(name="Administration: Users")
*
* @OA\Tag(name="Miscellaneous")
*
* @OA\SecurityScheme(
* type="apiKey",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -37,10 +37,9 @@
<script src="./swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
// Begin Swagger UI call region
const ui = SwaggerUIBundle({
url: "openapi.yml",
url: "/api/openapi.yml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
@ -52,6 +51,7 @@
],
layout: "StandaloneLayout"
})
// End Swagger UI call region
window.ui = ui
}

View File

@ -9,7 +9,7 @@ info:
servers:
-
url: 'https://demo.azuracast.com/api'
description: 'AzuraCast Demo API'
description: 'AzuraCast Public Demo Server'
paths:
/status:
get:
@ -49,8 +49,7 @@ paths:
description: 'Access denied'
security:
-
api_key:
- 'view station reports'
- api_key
/nowplaying:
get:
tags:
@ -159,6 +158,25 @@ paths:
description: 'The requested album artwork'
'404':
description: 'Image not found; generic filler image.'
'/station/{station_id}/queue':
get:
tags:
- 'Stations: Listeners'
description: 'Return information about the upcoming song playback queue.'
operationId: 'App\Controller\Api\Stations\QueueController::listAction'
parameters:
-
$ref: '#/components/parameters/station_id_required'
responses:
'200':
description: Success
'404':
description: 'Station not found'
'403':
description: 'Access denied'
security:
-
- api_key
'/station/{station_id}/restart':
post:
tags:
@ -285,7 +303,7 @@ components:
connected_on:
description: 'UNIX timestamp that the user first connected.'
type: integer
example: 1545441844
example: 1545718940
connected_time:
description: 'Number of seconds that the user has been connected.'
type: integer
@ -381,7 +399,7 @@ components:
cued_at:
description: 'UNIX timestamp when the item was cued for playback.'
type: integer
example: 1545441844
example: 1545718940
autodj_custom_uri:
description: 'Custom AutoDJ playback URI, if it exists.'
type: string
@ -436,7 +454,7 @@ components:
played_at:
description: 'UNIX timestamp when playback started.'
type: integer
example: 1545441844
example: 1545718940
duration:
description: 'Duration of the song in seconds'
type: integer
@ -563,7 +581,7 @@ components:
timestamp:
description: 'The current UNIX timestamp'
type: integer
example: 1545441844
example: 1545718940
type: object
Api_Time:
properties:
@ -629,10 +647,10 @@ components:
example: dark
created_at:
type: integer
example: 1545441844
example: 1545718940
updated_at:
type: integer
example: 1545441844
example: 1545718940
roles:
items: { }
type: object

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long