Merge commit '28de7fdf86dfcd38fb3f9c30ac30a311e4c23467' into ci-testing

This commit is contained in:
Buster "Silver Eagle" Neece 2021-12-12 21:40:03 -06:00
commit 4d3a16f3cb
No known key found for this signature in database
GPG Key ID: 9FC8B9E008872109
90 changed files with 2649 additions and 2307 deletions

View File

@ -36,7 +36,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
php-version: '8.1'
extensions: intl, maxminddb
tools: composer:v2, cs2pr
@ -110,6 +110,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
id: qemu
with:
platforms: arm64
@ -182,7 +183,7 @@ jobs:
with:
context: .
push: true
platforms : linux/amd64,linux/arm64
# platforms : linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=ghcr.io/azuracast/web:buildcache

View File

@ -10,7 +10,7 @@
}
],
"require": {
"php": "^8.0",
"php": "^8.1",
"ext-PDO": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
@ -89,7 +89,7 @@
"theiconic/php-ga-measurement-protocol": "^2.9",
"vlucas/phpdotenv": "^5.3",
"wikimedia/composer-merge-plugin": "dev-master",
"zircote/swagger-php": "^3"
"zircote/swagger-php": "^4"
},
"replace": {
"symfony/polyfill-iconv": "1.99",

413
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,9 @@ parameters:
bootstrapFiles:
- ./util/phpstan.php
scanDirectories:
- ./vendor/zircote/swagger-php/src/Annotations
universalObjectCratesClasses:
- App\Session\NamespaceInterface
- App\View

View File

@ -143,10 +143,10 @@ class Acl
*/
public function userAllowed(
?Entity\User $user = null,
array|string $action,
array|string $action = null,
Entity\Station|int $stationId = null
): bool {
if (null === $user) {
if (null === $user || null === $action) {
return false;
}

View File

@ -225,7 +225,11 @@ class AppFactory
protected static function applyPhpSettings(Environment $environment): void
{
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT);
error_reporting(
$environment->isProduction()
? E_ALL & ~E_NOTICE & ~E_WARNING & ~E_STRICT & ~E_DEPRECATED
: E_ALL & ~E_NOTICE
);
$displayStartupErrors = (!$environment->isProduction() || $environment->isCli())
? '1'
@ -240,13 +244,16 @@ class AppFactory
? '/dev/stderr'
: $environment->getTempDirectory() . '/php_errors.log'
);
ini_set('session.use_only_cookies', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_lifetime', '86400');
ini_set('session.use_strict_mode', '1');
if (!headers_sent()) {
ini_set('session.use_only_cookies', '1');
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_lifetime', '86400');
ini_set('session.use_strict_mode', '1');
session_cache_limiter('');
}
date_default_timezone_set('UTC');
session_cache_limiter('');
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Assets;
use App\Environment;
use InvalidArgumentException;
class AssetFactory
{
@ -33,7 +34,7 @@ class AssetFactory
self::TYPE_ALBUM_ART => self::createAlbumArt($environment),
self::TYPE_BACKGROUND => self::createBackground($environment),
self::TYPE_BROWSER_ICON => self::createBrowserIcon($environment),
default => throw new \InvalidArgumentException('Invalid type specified.')
default => throw new InvalidArgumentException('Invalid type specified.')
};
}
}

View File

@ -6,6 +6,7 @@ namespace App\Assets;
use App\Utilities\File;
use Intervention\Image\Image;
use RuntimeException;
class BrowserIconCustomAsset extends AbstractCustomAsset
{
@ -42,7 +43,7 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
{
$uploadsDir = $this->environment->getUploadsDirectory() . '/browser_icon';
if (!mkdir($uploadsDir) && !is_dir($uploadsDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $uploadsDir));
throw new RuntimeException(sprintf('Directory "%s" was not created', $uploadsDir));
}
$newImage = clone $image;

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Console;
use RuntimeException;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\StreamOutput;
@ -25,7 +26,7 @@ class Application extends \Silly\Edition\PhpDi\Application
$temp_stream = fopen($outputFile, 'wb+');
if (false === $temp_stream) {
throw new \RuntimeException(sprintf('Could not open output file: "%s"', $outputFile));
throw new RuntimeException(sprintf('Could not open output file: "%s"', $outputFile));
}
$output = new StreamOutput($temp_stream);

View File

@ -135,6 +135,23 @@ class BackupCommand extends CommandAbstract
);
switch ($file_ext) {
case 'tzst':
$this->passThruProcess(
$io,
array_merge(
[
'tar',
'-I',
'zstd',
'-cf',
$tmpPath,
],
$files_to_backup
),
'/'
);
break;
case 'gz':
case 'tgz':
$this->passThruProcess(

View File

@ -43,6 +43,20 @@ class RestoreCommand extends CommandAbstract
$file_ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
switch ($file_ext) {
case 'tzst':
$this->passThruProcess(
$io,
[
'tar',
'-I',
'unzstd',
'-xvf',
$path,
],
'/'
);
break;
case 'gz':
case 'tgz':
$this->passThruProcess(

View File

@ -10,6 +10,7 @@ use App\Version;
use OpenApi\Annotations\OpenApi;
use OpenApi\Generator;
use OpenApi\Util;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class GenerateApiDocsCommand extends CommandAbstract
@ -17,14 +18,15 @@ class GenerateApiDocsCommand extends CommandAbstract
public function __construct(
Application $application,
protected Environment $environment,
protected Version $version
protected Version $version,
protected LoggerInterface $logger
) {
parent::__construct($application);
}
public function __invoke(SymfonyStyle $io): int
{
$yaml = $this->generate()->toYaml();
$yaml = $this->generate()?->toYaml();
$yaml_path = $this->environment->getBaseDirectory() . '/web/static/api/openapi.yml';
file_put_contents($yaml_path, $yaml);
@ -36,7 +38,7 @@ class GenerateApiDocsCommand extends CommandAbstract
public function generate(
bool $useCurrentVersion = false,
string $apiBaseUrl = 'https://demo.azuracast.com/api'
): OpenApi {
): ?OpenApi {
define('AZURACAST_API_URL', $apiBaseUrl);
define('AZURACAST_API_NAME', 'AzuraCast Public Demo Server');
define(
@ -57,6 +59,8 @@ class GenerateApiDocsCommand extends CommandAbstract
]
);
return Generator::scan($finder);
return Generator::scan($finder, [
'logger' => $this->logger,
]);
}
}

View File

@ -31,12 +31,12 @@ class GenerateCommand extends CommandAbstract
$directory = new RecursiveDirectoryIterator($environment->getBaseDirectory() . '/frontend/vue');
$iterator = new RecursiveIteratorIterator($directory);
$vueRegex = new RegexIterator($iterator, '/^.+\.(vue)$/i', RecursiveRegexIterator::GET_MATCH);
$vueRegex = new RegexIterator($iterator, '/^.+\.(vue)$/i', RegexIterator::GET_MATCH);
foreach ($vueRegex as $pathMatch) {
$translations->addFromVueJsFile($pathMatch[0]);
}
$jsRegex = new RegexIterator($iterator, '/^.+\.(js)$/i', RecursiveRegexIterator::GET_MATCH);
$jsRegex = new RegexIterator($iterator, '/^.+\.(js)$/i', RegexIterator::GET_MATCH);
foreach ($jsRegex as $pathMatch) {
$translations->addFromJsCodeFile($pathMatch[0]);
}
@ -51,7 +51,7 @@ class GenerateCommand extends CommandAbstract
foreach ($translatable_folders as $folder) {
$directory = new RecursiveDirectoryIterator($folder);
$iterator = new RecursiveIteratorIterator($directory);
$regex = new RegexIterator($iterator, '/^.+\.(phtml|php)$/i', RecursiveRegexIterator::GET_MATCH);
$regex = new RegexIterator($iterator, '/^.+\.(phtml|php)$/i', RegexIterator::GET_MATCH);
foreach ($regex as $path_match) {
$path = $path_match[0];

View File

@ -14,6 +14,9 @@ use App\Utilities;
use Doctrine\ORM\Query;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Stringable;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -104,8 +107,8 @@ abstract class AbstractApiCrudController
array_merge(
$context,
[
ObjectNormalizer::ENABLE_MAX_DEPTH => true,
ObjectNormalizer::MAX_DEPTH_HANDLER => function (
AbstractObjectNormalizer::ENABLE_MAX_DEPTH => true,
AbstractObjectNormalizer::MAX_DEPTH_HANDLER => function (
$innerObject,
$outerObject,
string $attributeName,
@ -114,7 +117,7 @@ abstract class AbstractApiCrudController
) {
return $this->displayShortenedObject($innerObject);
},
ObjectNormalizer::CIRCULAR_REFERENCE_HANDLER => function (
AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function (
$object,
string $format = null,
array $context = []
@ -140,7 +143,7 @@ abstract class AbstractApiCrudController
return $object->getIdRequired();
}
if ($object instanceof \Stringable) {
if ($object instanceof Stringable) {
return (string)$object;
}
@ -183,7 +186,7 @@ abstract class AbstractApiCrudController
protected function fromArray(array $data, ?object $record = null, array $context = []): object
{
if (null !== $record) {
$context[ObjectNormalizer::OBJECT_TO_POPULATE] = $record;
$context[AbstractNormalizer::OBJECT_TO_POPULATE] = $record;
}
return $this->serializer->denormalize($data, $this->entityClass, null, $context);

View File

@ -7,6 +7,7 @@ namespace App\Controller\Api\Admin\Backups;
use App\Entity;
use App\Exception\NotFoundException;
use Azura\Files\ExtendedFilesystemInterface;
use InvalidArgumentException;
use JetBrains\PhpStorm\ArrayShape;
abstract class AbstractFileAction
@ -28,7 +29,7 @@ abstract class AbstractFileAction
);
if (!($storageLocation instanceof Entity\StorageLocation)) {
throw new \InvalidArgumentException('Invalid storage location.');
throw new InvalidArgumentException('Invalid storage location.');
}
$fs = $storageLocation->getFilesystem();

View File

@ -8,88 +8,92 @@ use App\Entity;
use OpenApi\Annotations as OA;
/**
* @OA\Get(path="/admin/custom_fields",
* operationId="getCustomFields",
* tags={"Administration: Custom Fields"},
* description="List all current custom fields in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/CustomField"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/custom_fields",
* operationId="addCustomField",
* tags={"Administration: Custom Fields"},
* description="Create a new custom field.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/custom_field/{id}",
* operationId="getCustomField",
* tags={"Administration: Custom Fields"},
* description="Retrieve details for a single custom field.",
* @OA\Parameter(
* name="id",
* in="path",
* description="ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/custom_field/{id}",
* operationId="editCustomField",
* tags={"Administration: Custom Fields"},
* description="Update details of a single custom field.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*
* @OA\Delete(path="/admin/custom_field/{id}",
* operationId="deleteCustomField",
* tags={"Administration: Custom Fields"},
* description="Delete a single custom field.",
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*
* @extends AbstractAdminApiCrudController<Entity\CustomField>
*/
class CustomFieldsController extends AbstractAdminApiCrudController
{
protected string $entityClass = Entity\CustomField::class;
protected string $resourceRouteName = 'api:admin:custom_field';
/**
* @OA\Get(path="/admin/custom_fields",
* tags={"Administration: Custom Fields"},
* description="List all current custom fields in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/CustomField"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/custom_fields",
* tags={"Administration: Custom Fields"},
* description="Create a new custom field.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/custom_field/{id}",
* tags={"Administration: Custom Fields"},
* description="Retrieve details for a single custom field.",
* @OA\Parameter(
* name="id",
* in="path",
* description="ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/custom_field/{id}",
* tags={"Administration: Custom Fields"},
* description="Update details of a single custom field.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/CustomField")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
* @OA\Delete(path="/admin/custom_field/{id}",
* tags={"Administration: Custom Fields"},
* description="Delete a single custom field.",
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*/
}

View File

@ -10,23 +10,21 @@ use App\Http\ServerRequest;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/admin/permissions",
* operationId="getPermissions",
* tags={"Administration: Roles"},
* description="Return a list of all available permissions.",
* @OA\Response(
* response=200,
* description="Success",
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
class PermissionsController
{
/**
* @OA\Get(path="/admin/permissions",
* tags={"Administration: Roles"},
* description="Return a list of all available permissions.",
* @OA\Response(
* response=200,
* description="Success",
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @param ServerRequest $request
* @param Response $response
*/
public function __invoke(
ServerRequest $request,
Response $response,

View File

@ -13,6 +13,19 @@ use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/internal/relays",
* operationId="internalGetRelayDetails",
* tags={"Administration: Relays"},
* description="Returns all necessary information to relay all 'relayable' stations.",
* parameters={},
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Admin_Relay"))
* )
* )
*/
class RelaysController
{
public function __construct(
@ -21,21 +34,6 @@ class RelaysController
) {
}
/**
* @OA\Get(path="/internal/relays",
* tags={"Administration: Relays"},
* description="Returns all necessary information to relay all 'relayable' stations.",
* parameters={},
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Admin_Relay"))
* )
* )
*
* @param ServerRequest $request
* @param Response $response
*/
public function __invoke(ServerRequest $request, Response $response): ResponseInterface
{
$stations = $this->getManageableStations($request);

View File

@ -7,12 +7,96 @@ namespace App\Controller\Api\Admin;
use App\Acl;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use InvalidArgumentException;
use OpenApi\Annotations as OA;
use RuntimeException;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/admin/roles",
* operationId="getRoles",
* tags={"Administration: Roles"},
* description="List all current roles in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Role"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/roles",
* operationId="addRole",
* tags={"Administration: Roles"},
* description="Create a new role.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/role/{id}",
* operationId="getRole",
* tags={"Administration: Roles"},
* description="Retrieve details for a single current role.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Role ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/role/{id}",
* operationId="editRole",
* tags={"Administration: Roles"},
* description="Update details of a single role.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Role 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": {}}},
* )
*
* @OA\Delete(path="/admin/role/{id}",
* operationId="deleteRole",
* tags={"Administration: Roles"},
* description="Delete a single role.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Role 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": {}}},
* )
*
* @extends AbstractAdminApiCrudController<Entity\Role>
*/
class RolesController extends AbstractAdminApiCrudController
@ -30,96 +114,15 @@ class RolesController extends AbstractAdminApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/admin/roles",
* tags={"Administration: Roles"},
* description="List all current roles in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Role"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/roles",
* tags={"Administration: Roles"},
* description="Create a new role.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/role/{id}",
* tags={"Administration: Roles"},
* description="Retrieve details for a single current role.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Role ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/role/{id}",
* tags={"Administration: Roles"},
* description="Update details of a single role.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Role")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Role 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": {}}},
* )
*
* @OA\Delete(path="/admin/role/{id}",
* tags={"Administration: Roles"},
* description="Delete a single role.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Role 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": {}}},
* )
*
* @inheritdoc
*/
protected function deleteRecord(object $record): void
{
if (!($record instanceof Entity\Role)) {
throw new \InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
}
$superAdminRole = $this->permissionRepo->ensureSuperAdministratorRole();
if ($superAdminRole->getIdRequired() === $record->getIdRequired()) {
throw new \RuntimeException('Cannot remove the Super Administrator role.');
throw new RuntimeException('Cannot remove the Super Administrator role.');
}
parent::deleteRecord($record);

View File

@ -7,7 +7,6 @@ namespace App\Controller\Api\Admin;
use App\Controller\Api\AbstractApiCrudController;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Exception\ValidationException;
use App\Http\Response;
use App\Http\ServerRequest;
use OpenApi\Annotations as OA;
@ -17,6 +16,31 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/admin/settings",
* operationId="getSettings",
* tags={"Administration: Settings"},
* description="List the current values of all editable system settings.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Settings")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/settings",
* operationId="editSettings",
* tags={"Administration: Settings"},
* description="Update settings to modify any settings provided.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Settings")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @extends AbstractApiCrudController<Entity\Settings>
*/
class SettingsController extends AbstractApiCrudController
@ -32,20 +56,6 @@ class SettingsController extends AbstractApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/admin/settings",
* tags={"Administration: Settings"},
* description="List the current values of all editable system settings.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Settings")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @param ServerRequest $request
* @param Response $response
*/
public function listAction(
ServerRequest $request,
Response $response,
@ -60,25 +70,6 @@ class SettingsController extends AbstractApiCrudController
return $response->withJson($this->toArray($settings, $context));
}
/**
* @OA\Put(path="/admin/settings",
* tags={"Administration: Settings"},
* description="Update settings to modify any settings provided.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Settings")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @param ServerRequest $request
* @param Response $response
*
* @throws ValidationException
*/
public function updateAction(
ServerRequest $request,
Response $response,

View File

@ -18,6 +18,88 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/admin/stations",
* operationId="adminGetStations",
* tags={"Administration: Stations"},
* description="List all current stations in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Station"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/stations",
* operationId="adminAddStation",
* tags={"Administration: Stations"},
* description="Create a new station.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/station/{id}",
* operationId="adminGetStation",
* tags={"Administration: Stations"},
* description="Retrieve details for a single station.",
* @OA\Parameter(
* name="id",
* in="path",
* description="ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/station/{id}",
* operationId="adminEditStation",
* tags={"Administration: Stations"},
* description="Update details of a single station.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*
* @OA\Delete(path="/admin/station/{id}",
* operationId="adminDeleteStation",
* tags={"Administration: Stations"},
* description="Delete a single station.",
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*
* @extends AbstractAdminApiCrudController<Entity\Station>
*/
class StationsController extends AbstractAdminApiCrudController
@ -37,85 +119,6 @@ class StationsController extends AbstractAdminApiCrudController
parent::__construct($reloadableEm, $serializer, $validator);
}
/**
* @OA\Get(path="/admin/stations",
* tags={"Administration: Stations"},
* description="List all current stations in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Station"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/stations",
* tags={"Administration: Stations"},
* description="Create a new station.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/station/{id}",
* tags={"Administration: Stations"},
* description="Retrieve details for a single station.",
* @OA\Parameter(
* name="id",
* in="path",
* description="ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/station/{id}",
* tags={"Administration: Stations"},
* description="Update details of a single station.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Station")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*
* @OA\Delete(path="/admin/station/{id}",
* tags={"Administration: Stations"},
* description="Delete a single station.",
* @OA\Parameter(
* name="id",
* in="path",
* description="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": {}}},
* )
*/
protected function viewRecord(object $record, ServerRequest $request): mixed
{
if (!($record instanceof $this->entityClass)) {

View File

@ -16,6 +16,88 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/admin/storage_locations",
* operationId="getStorageLocations",
* tags={"Administration: Storage Locations"},
* description="List all current storage locations in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Admin_StorageLocation"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/storage_locations",
* operationId="addStorageLocation",
* tags={"Administration: Storage Locations"},
* description="Create a new storage location.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/storage_location/{id}",
* operationId="getStorageLocation",
* tags={"Administration: Storage Locations"},
* description="Retrieve details for a single storage location.",
* @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_Admin_StorageLocation")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/storage_location/{id}",
* operationId="editStorageLocation",
* tags={"Administration: Storage Locations"},
* description="Update details of a single storage location.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Storage Location 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": {}}},
* )
*
* @OA\Delete(path="/admin/storage_location/{id}",
* operationId="deleteStorageLocation",
* tags={"Administration: Storage Locations"},
* description="Delete a single storage location.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Storage Location 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": {}}},
* )
*
* @extends AbstractAdminApiCrudController<Entity\StorageLocation>
*/
class StorageLocationsController extends AbstractAdminApiCrudController
@ -32,85 +114,6 @@ class StorageLocationsController extends AbstractAdminApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/admin/storage_locations",
* tags={"Administration: Storage Locations"},
* description="List all current storage locations in the system.",
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Admin_StorageLocation"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/storage_locations",
* tags={"Administration: Storage Locations"},
* description="Create a new storage location.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/admin/storage_location/{id}",
* tags={"Administration: Storage Locations"},
* description="Retrieve details for a single storage location.",
* @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_Admin_StorageLocation")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/admin/storage_location/{id}",
* tags={"Administration: Storage Locations"},
* description="Update details of a single storage location.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Admin_StorageLocation")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Storage Location 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": {}}},
* )
*
* @OA\Delete(path="/admin/storage_location/{id}",
* tags={"Administration: Storage Locations"},
* description="Delete a single storage location.",
* @OA\Parameter(
* name="id",
* in="path",
* description="Storage Location 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": {}}},
* )
*/
public function listAction(ServerRequest $request, Response $response): ResponseInterface
{
$qb = $this->em->createQueryBuilder();

View File

@ -8,10 +8,93 @@ use App\Controller\Frontend\Account\MasqueradeAction;
use App\Entity;
use App\Http\Response;
use App\Http\ServerRequest;
use InvalidArgumentException;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/admin/users",
* operationId="getUsers",
* 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=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/users",
* operationId="addUser",
* tags={"Administration: Users"},
* description="Create a new user.",
* @OA\RequestBody(
* @OA\JsonContent(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": {}}},
* )
*
* @OA\Get(path="/admin/user/{id}",
* operationId="getUser",
* 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": {}}},
* )
*
* @OA\Put(path="/admin/user/{id}",
* operationId="editUser",
* tags={"Administration: Users"},
* description="Update details of a single user.",
* @OA\RequestBody(
* @OA\JsonContent(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": {}}},
* )
*
* @OA\Delete(path="/admin/user/{id}",
* operationId="deleteUser",
* 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": {}}},
* )
*
* @extends AbstractAdminApiCrudController<Entity\User>
*/
class UsersController extends AbstractAdminApiCrudController
@ -19,72 +102,10 @@ class UsersController extends AbstractAdminApiCrudController
protected string $entityClass = Entity\User::class;
protected string $resourceRouteName = 'api:admin:user';
/**
* @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=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/admin/users",
* tags={"Administration: Users"},
* description="Create a new user.",
* @OA\RequestBody(
* @OA\JsonContent(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": {}}},
* )
*
* @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": {}}},
* )
*
* @OA\Put(path="/admin/user/{id}",
* tags={"Administration: Users"},
* description="Update details of a single user.",
* @OA\RequestBody(
* @OA\JsonContent(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": {}}},
* )
*/
protected function viewRecord(object $record, ServerRequest $request): mixed
{
if (!($record instanceof Entity\User)) {
throw new \InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
}
$return = $this->toArray($record);
@ -135,26 +156,6 @@ class UsersController extends AbstractAdminApiCrudController
return $response->withJson(Entity\Api\Status::updated());
}
/**
* @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": {}}},
* )
*
* @inheritdoc
*/
public function deleteAction(ServerRequest $request, Response $response, mixed $id): ResponseInterface
{
$record = $this->getRecord($id);

View File

@ -8,7 +8,9 @@ use App\Controller\Api\Admin\UsersController;
use App\Entity;
use App\Http\Response;
use App\Http\ServerRequest;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class PutPasswordAction extends UsersController
{
@ -19,16 +21,16 @@ class PutPasswordAction extends UsersController
try {
if (empty($body['current_password'])) {
throw new \InvalidArgumentException('Current password not provided (current_password).');
throw new InvalidArgumentException('Current password not provided (current_password).');
}
$currentPassword = $body['current_password'];
if (!$user->verifyPassword($currentPassword)) {
throw new \InvalidArgumentException('Invalid current password.');
throw new InvalidArgumentException('Invalid current password.');
}
if (empty($body['new_password'])) {
throw new \InvalidArgumentException('New password not provided (new_password).');
throw new InvalidArgumentException('New password not provided (new_password).');
}
$user = $this->em->refetch($user);
@ -38,7 +40,7 @@ class PutPasswordAction extends UsersController
$this->em->flush();
return $response->withJson(Entity\Api\Status::updated());
} catch (\Throwable $e) {
} catch (Throwable $e) {
return $response->withStatus(400)->withJson(Entity\Api\Error::fromException($e));
}
}

View File

@ -10,9 +10,11 @@ use App\Entity;
use App\Http\Response;
use App\Http\ServerRequest;
use BaconQrCode;
use InvalidArgumentException;
use OTPHP\TOTP;
use ParagonIE\ConstantTime\Base32;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class PutTwoFactorAction extends UsersController
{
@ -26,7 +28,7 @@ class PutTwoFactorAction extends UsersController
if (!empty($params['secret'])) {
$secret = $params['secret'];
if (64 !== strlen($secret)) {
throw new \InvalidArgumentException('Secret is not the correct length.');
throw new InvalidArgumentException('Secret is not the correct length.');
}
} else {
// Generate new TOTP secret.
@ -52,7 +54,7 @@ class PutTwoFactorAction extends UsersController
return $response->withJson(Entity\Api\Status::success());
}
throw new \InvalidArgumentException('Could not verify TOTP code.');
throw new InvalidArgumentException('Could not verify TOTP code.');
}
// Further customize TOTP code (with metadata that won't be stored in the DB)
@ -74,7 +76,7 @@ class PutTwoFactorAction extends UsersController
'totp_uri' => $totp_uri,
'qr_code' => $qrCodeBase64,
]);
} catch (\Throwable $e) {
} catch (Throwable $e) {
return $response->withStatus(400)->withJson(Entity\Api\Error::fromException($e));
}
}

View File

@ -22,9 +22,9 @@ class OpenApiAction
(string)$request->getRouter()->fromHere(absolute: true)
);
$yaml = $apiDocsCommand->generate(true, $apiBaseUrl)->toYaml();
$yaml = $apiDocsCommand->generate(true, $apiBaseUrl)?->toYaml();
$response->getBody()->write($yaml);
$response->getBody()->write($yaml ?? '');
return $response->withHeader('Content-Type', 'text/x-yaml');
}
}

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Art;
use App\Entity;
use App\Exception\NoFileUploadedException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Service\Flow;
@ -21,7 +22,7 @@ class PostArtAction
* @param int|string $media_id
*
* @return ResponseInterface
* @throws \App\Exception\NoFileUploadedException
* @throws NoFileUploadedException
*/
public function __invoke(
ServerRequest $request,

View File

@ -10,6 +10,7 @@ use App\Http\Response;
use App\Http\ServerRequest;
use App\Sync\Task\RunAutomatedAssignmentTask;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class RunAction extends StationsController
{
@ -23,7 +24,7 @@ class RunAction extends StationsController
try {
$syncTask->runStation($station, true);
return $response->withJson(Entity\Api\Status::success());
} catch (\Throwable $e) {
} catch (Throwable $e) {
return $response->withStatus(400)->withJson(Entity\Api\Error::fromException($e));
}
}

View File

@ -22,6 +22,93 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/station/{station_id}/files",
* operationId="getFiles",
* tags={"Stations: Media"},
* description="List all current uploaded files.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationMedia"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/files",
* operationId="addFile",
* tags={"Stations: Media"},
* description="Upload a new file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_UploadFile")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/file/{id}",
* operationId="getFile",
* tags={"Stations: Media"},
* description="Retrieve details for a single file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/file/{id}",
* operationId="editFile",
* tags={"Stations: Media"},
* description="Update details of a single file.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/file/{id}",
* operationId="deleteFile",
* tags={"Stations: Media"},
* description="Delete a single file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media 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": {}}},
* )
*
* @extends AbstractStationApiCrudController<Entity\StationMedia>
*/
class FilesController extends AbstractStationApiCrudController
@ -42,95 +129,6 @@ class FilesController extends AbstractStationApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/station/{station_id}/files",
* tags={"Stations: Media"},
* description="List all current uploaded files.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationMedia"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/files",
* tags={"Stations: Media"},
* description="Upload a new file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_UploadFile")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/file/{id}",
* tags={"Stations: Media"},
* description="Retrieve details for a single file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/file/{id}",
* tags={"Stations: Media"},
* description="Update details of a single file.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMedia")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/file/{id}",
* tags={"Stations: Media"},
* description="Delete a single file.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Media 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": {}}},
* )
*/
/**
* @param ServerRequest $request
* @param Response $response
*
*/
public function listAction(ServerRequest $request, Response $response): ResponseInterface
{
$storageLocation = $this->getStation($request)->getMediaStorageLocation();

View File

@ -15,6 +15,40 @@ use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/station/{station_id}/history",
* operationId="getStationHistory",
* tags={"Stations: History"},
* description="Return song playback history items for a given station.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="start",
* description="The start date for records, in YYYY-MM-DD format.",
* in="query",
* required=false,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="end",
* description="The end date for records, in YYYY-MM-DD format.",
* in="query",
* required=false,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_DetailedSongHistory"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
class HistoryController
{
public function __construct(
@ -24,38 +58,6 @@ class HistoryController
}
/**
* @OA\Get(path="/station/{station_id}/history",
* tags={"Stations: History"},
* description="Return song playback history items for a given station.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="start",
* description="The start date for records, in YYYY-MM-DD format.",
* in="query",
* required=false,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Parameter(
* name="end",
* description="The end date for records, in YYYY-MM-DD format.",
* in="query",
* required=false,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_DetailedSongHistory"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @param ServerRequest $request
* @param Response $response
*/

View File

@ -5,14 +5,36 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Entity;
use App\Exception;
use App\Exception\NotFoundException;
use App\Http\Response;
use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/stations",
* operationId="getStations",
* tags={"Stations: General"},
* description="Returns a list of stations.",
* parameters={},
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_NowPlaying_Station")
* )
* )
* )
*
* @OA\Get(path="/station/{station_id}",
* operationId="getStation",
* 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\JsonContent(ref="#/components/schemas/Api_NowPlaying_Station")
* ),
* @OA\Response(response=404, description="Station not found")
* )
*/
class IndexController
{
public function __construct(
@ -21,23 +43,6 @@ class IndexController
) {
}
/**
* @OA\Get(path="/stations",
* tags={"Stations: General"},
* description="Returns a list of stations.",
* parameters={},
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_NowPlaying_Station")
* )
* )
* )
* @param ServerRequest $request
* @param Response $response
*
* @throws NotFoundException
* @throws Exception
*/
public function listAction(ServerRequest $request, Response $response): ResponseInterface
{
$stations_raw = $this->em->getRepository(Entity\Station::class)
@ -57,21 +62,6 @@ class IndexController
return $response->withJson($stations);
}
/**
* @OA\Get(path="/station/{station_id}",
* 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\JsonContent(ref="#/components/schemas/Api_NowPlaying_Station")
* ),
* @OA\Response(response=404, description="Station not found")
* )
* @param ServerRequest $request
* @param Response $response
*
* @throws Exception
*/
public function indexAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();

View File

@ -18,24 +18,26 @@ use GuzzleHttp\Psr7\Stream;
use League\Csv\Writer;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
/**
* @OA\Get(path="/station/{station_id}/listeners",
* operationId="getStationListeners",
* 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\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Listener"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
class ListenersAction
{
/**
* @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\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Listener"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
public function __invoke(
ServerRequest $request,
Response $response,
@ -240,7 +242,7 @@ class ListenersAction
array $listeners,
string $filename
): ResponseInterface {
$tempFile = tmpfile() ?: throw new \RuntimeException('Could not create temp file.');
$tempFile = tmpfile() ?: throw new RuntimeException('Could not create temp file.');
$csv = Writer::createFromStream($tempFile);
$tz = $station->getTimezoneObject();

View File

@ -17,6 +17,93 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/station/{station_id}/mounts",
* operationId="getStationMounts",
* tags={"Stations: Mount Points"},
* description="List all current mount points.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationMount"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/mounts",
* operationId="addMount",
* tags={"Stations: Mount Points"},
* description="Create a new mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/mount/{id}",
* operationId="getMount",
* tags={"Stations: Mount Points"},
* description="Retrieve details for a single mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/mount/{id}",
* operationId="editMount",
* tags={"Stations: Mount Points"},
* description="Update details of a single mount point.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/mount/{id}",
* operationId="deleteMount",
* tags={"Stations: Mount Points"},
* description="Delete a single mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="StationMount 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": {}}},
* )
*
* @extends AbstractStationApiCrudController<Entity\StationMount>
*/
class MountsController extends AbstractStationApiCrudController
@ -33,90 +120,6 @@ class MountsController extends AbstractStationApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/station/{station_id}/mounts",
* tags={"Stations: Mount Points"},
* description="List all current mount points.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationMount"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/mounts",
* tags={"Stations: Mount Points"},
* description="Create a new mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/mount/{id}",
* tags={"Stations: Mount Points"},
* description="Retrieve details for a single mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/mount/{id}",
* tags={"Stations: Mount Points"},
* description="Update details of a single mount point.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationMount")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/mount/{id}",
* tags={"Stations: Mount Points"},
* description="Delete a single mount point.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="StationMount 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": {}}},
* )
*/
protected function viewRecord(object $record, ServerRequest $request): mixed
{
/** @var Entity\StationMount $record */

View File

@ -82,7 +82,7 @@ class ImportAction extends AbstractPlaylistsAction
// Work backwards from the basename to try to find matches.
$pathParts = explode('/', $path_raw);
$basename = File::sanitizeFileName(array_pop($pathParts));
array_push($pathParts, $basename);
$pathParts[] = $basename;
// Attempt full path matching if possible
if (count($pathParts) >= 2) {
@ -98,7 +98,6 @@ class ImportAction extends AbstractPlaylistsAction
// Attempt basename-only matching
if (isset($basenameLookup[$basename])) {
$matches[] = $basenameLookup[$basename];
continue;
}
}

View File

@ -14,6 +14,93 @@ use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
/**
* @OA\Get(path="/station/{station_id}/playlists",
* operationId="getPlaylists",
* tags={"Stations: Playlists"},
* description="List all current playlists.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationPlaylist"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/playlists",
* operationId="addPlaylist",
* tags={"Stations: Playlists"},
* description="Create a new playlist.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/playlist/{id}",
* operationId="getPlaylist",
* tags={"Stations: Playlists"},
* description="Retrieve details for a single playlist.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/playlist/{id}",
* operationId="editPlaylist",
* tags={"Stations: Playlists"},
* description="Update details of a single playlist.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/playlist/{id}",
* operationId="deletePlaylist",
* tags={"Stations: Playlists"},
* description="Delete a single playlist relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist 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": {}}},
* )
*
* @extends AbstractScheduledEntityController<Entity\StationPlaylist>
*/
class PlaylistsController extends AbstractScheduledEntityController
@ -21,90 +108,6 @@ class PlaylistsController extends AbstractScheduledEntityController
protected string $entityClass = Entity\StationPlaylist::class;
protected string $resourceRouteName = 'api:stations:playlist';
/**
* @OA\Get(path="/station/{station_id}/playlists",
* tags={"Stations: Playlists"},
* description="List all current playlists.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationPlaylist"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/playlists",
* tags={"Stations: Playlists"},
* description="Create a new playlist.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/playlist/{id}",
* tags={"Stations: Playlists"},
* description="Retrieve details for a single playlist.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/playlist/{id}",
* tags={"Stations: Playlists"},
* description="Update details of a single playlist.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationPlaylist")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/playlist/{id}",
* tags={"Stations: Playlists"},
* description="Delete a single playlist relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Playlist 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": {}}},
* )
*/
/**
* @inheritDoc
*/

View File

@ -13,11 +13,135 @@ use App\Http\Response;
use App\Http\ServerRequest;
use App\Service\Flow\UploadedFile;
use InvalidArgumentException;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/station/{station_id}/podcast/{podcast_id}/episodes",
* operationId="getEpisodes",
* tags={"Stations: Podcasts"},
* description="List all current episodes for a given podcast ID.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_PodcastEpisode"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/podcast/{podcast_id}/episodes",
* operationId="addEpisode",
* tags={"Stations: Podcasts"},
* description="Create a new podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* operationId="getEpisode",
* tags={"Stations: Podcasts"},
* description="Retrieve details for a single podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* operationId="editEpisode",
* tags={"Stations: Podcasts"},
* description="Update details of a single podcast episode.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* operationId="deleteEpisode",
* tags={"Stations: Podcasts"},
* description="Delete a single podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @extends AbstractApiCrudController<Entity\PodcastEpisode>
*/
class PodcastEpisodesController extends AbstractApiCrudController
@ -36,125 +160,6 @@ class PodcastEpisodesController extends AbstractApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/station/{station_id}/podcast/{podcast_id}/episodes",
* tags={"Stations: Podcasts"},
* description="List all current episodes for a given podcast ID.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_PodcastEpisode"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/podcast/{podcast_id}/episodes",
* tags={"Stations: Podcasts"},
* description="Create a new podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* tags={"Stations: Podcasts"},
* description="Retrieve details for a single podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* tags={"Stations: Podcasts"},
* description="Update details of a single podcast episode.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_PodcastEpisode")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/podcast/{podcast_id}/episode/{id}",
* tags={"Stations: Podcasts"},
* description="Delete a single podcast episode.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="podcast_id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast Episode ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
public function listAction(
ServerRequest $request,
Response $response,
@ -208,7 +213,7 @@ class PodcastEpisodesController extends AbstractApiCrudController
$podcast = $this->podcastRepository->fetchPodcastForStation($station, $podcast_id);
if (null === $podcast) {
throw new \RuntimeException('Podcast not found.');
throw new RuntimeException('Podcast not found.');
}
$parsedBody = (array)$request->getParsedBody();

View File

@ -20,6 +20,93 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/station/{station_id}/podcasts",
* operationId="getPodcasts",
* tags={"Stations: Podcasts"},
* description="List all current podcasts.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Podcast"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/podcasts",
* operationId="addPodcast",
* tags={"Stations: Podcasts"},
* description="Create a new podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/podcast/{id}",
* operationId="getPodcast",
* tags={"Stations: Podcasts"},
* description="Retrieve details for a single podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/podcast/{id}",
* operationId="editPodcast",
* tags={"Stations: Podcasts"},
* description="Update details of a single podcast.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/podcast/{id}",
* operationId="deletePodcast",
* tags={"Stations: Podcasts"},
* description="Delete a single podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @extends AbstractApiCrudController<Entity\Podcast>
*/
class PodcastsController extends AbstractApiCrudController
@ -37,93 +124,6 @@ class PodcastsController extends AbstractApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/station/{station_id}/podcasts",
* tags={"Stations: Podcasts"},
* description="List all current podcasts.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_Podcast"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/podcasts",
* tags={"Stations: Podcasts"},
* description="Create a new podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/podcast/{id}",
* tags={"Stations: Podcasts"},
* description="Retrieve details for a single podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/podcast/{id}",
* tags={"Stations: Podcasts"},
* description="Update details of a single podcast.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_Podcast")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/podcast/{id}",
* tags={"Stations: Podcasts"},
* description="Delete a single podcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Podcast ID",
* required=true,
* @OA\Schema(type="string")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_Status")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*/
/**
* @inheritDoc
*/
public function listAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();

View File

@ -15,6 +15,61 @@ use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @OA\Get(path="/station/{station_id}/queue",
* operationId="getQueue",
* 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\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_StationQueueDetailed")
* )
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}}
* )
*
* @OA\Get(path="/station/{station_id}/queue/{id}",
* operationId="getQueueItem",
* tags={"Stations: Queue"},
* description="Retrieve details of a single queued item.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @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_StationQueueDetailed")
* ),
* @OA\Response(response=404, description="Station or Queue ID not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}}
* )
*
* @OA\Delete(path="/station/{station_id}/queue/{id}",
* operationId="deleteQueueItem",
* tags={"Stations: Queue"},
* description="Delete a single queued item.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @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": {}}}
* )
*
* @extends AbstractStationApiCrudController<Entity\StationQueue>
*/
class QueueController extends AbstractStationApiCrudController
@ -32,23 +87,6 @@ class QueueController extends AbstractStationApiCrudController
parent::__construct($em, $serializer, $validator);
}
/**
* @OA\Get(path="/station/{station_id}/queue",
* 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\JsonContent(type="array",
* @OA\Items(ref="#/components/schemas/Api_StationQueueDetailed")
* )
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}}
* )
*
* @inheritdoc
*/
public function listAction(
ServerRequest $request,
Response $response
@ -63,46 +101,6 @@ class QueueController extends AbstractStationApiCrudController
);
}
/**
* @OA\Get(path="/station/{station_id}/queue/{id}",
* tags={"Stations: Queue"},
* description="Retrieve details of a single queued item.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @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_StationQueueDetailed")
* ),
* @OA\Response(response=404, description="Station or Queue ID not found"),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}}
* )
*
* @OA\Delete(path="/station/{station_id}/queue/{id}",
* tags={"Stations: Queue"},
* description="Delete a single queued item.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @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": {}}}
* )
*/
/**
* @param object $record
* @param ServerRequest $request

View File

@ -11,6 +11,93 @@ use InvalidArgumentException;
use OpenApi\Annotations as OA;
/**
* @OA\Get(path="/station/{station_id}/remotes",
* operationId="getRelays",
* tags={"Stations: Remote Relays"},
* description="List all current remote relays.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_StationRemote"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/remotes",
* operationId="addRelay",
* tags={"Stations: Remote Relays"},
* description="Create a new remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/remote/{id}",
* operationId="getRelay",
* tags={"Stations: Remote Relays"},
* description="Retrieve details for a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/remote/{id}",
* operationId="editRelay",
* tags={"Stations: Remote Relays"},
* description="Update details of a single remote relay.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/remote/{id}",
* operationId="deleteRelay",
* tags={"Stations: Remote Relays"},
* description="Delete a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @extends AbstractStationApiCrudController<Entity\StationRemote>
*/
class RemotesController extends AbstractStationApiCrudController
@ -18,90 +105,6 @@ class RemotesController extends AbstractStationApiCrudController
protected string $entityClass = Entity\StationRemote::class;
protected string $resourceRouteName = 'api:stations:remote';
/**
* @OA\Get(path="/station/{station_id}/remotes",
* tags={"Stations: Remote Relays"},
* description="List all current remote relays.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_StationRemote"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/remotes",
* tags={"Stations: Remote Relays"},
* description="Create a new remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/remote/{id}",
* tags={"Stations: Remote Relays"},
* description="Retrieve details for a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/remote/{id}",
* tags={"Stations: Remote Relays"},
* description="Update details of a single remote relay.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/Api_StationRemote")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/remote/{id}",
* tags={"Stations: Remote Relays"},
* description="Delete a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*/
protected function viewRecord(object $record, ServerRequest $request): mixed
{
if (!($record instanceof Entity\StationRemote)) {

View File

@ -14,6 +14,43 @@ use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/station/{station_id}/requests",
* operationId="getRequestableSongs",
* tags={"Stations: Song Requests"},
* description="Return a list of requestable songs.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* type="array",
* @OA\Items(ref="#/components/schemas/Api_StationRequest")
* )
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Station does not support requests")
* )
*
* @OA\Post(path="/station/{station_id}/request/{request_id}",
* operationId="submitSongRequest",
* tags={"Stations: Song Requests"},
* description="Submit a song request.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="request_id",
* description="The requestable song ID",
* in="path",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(response=200, description="Success"),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Station does not support requests")
* )
*/
class RequestsController
{
public function __construct(
@ -23,29 +60,6 @@ class RequestsController
) {
}
/**
* @OA\Get(path="/station/{station_id}/requests",
* tags={"Stations: Song Requests"},
* description="Return a list of requestable songs.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(
* type="array",
* @OA\Items(ref="#/components/schemas/Api_StationRequest")
* )
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Station does not support requests")
* )
*
* @param ServerRequest $request
* @param Response $response
*
* @throws Exception
* @throws Exception\InvalidRequestAttribute
*/
public function listAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();
@ -125,31 +139,6 @@ class RequestsController
return $paginator->write($response);
}
/**
* @OA\Post(path="/station/{station_id}/request/{request_id}",
* tags={"Stations: Song Requests"},
* description="Submit a song request.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="request_id",
* description="The requestable song ID",
* in="path",
* required=true,
* @OA\Schema(
* type="string"
* )
* ),
* @OA\Response(response=200, description="Success"),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Station does not support requests")
* )
*
* @param ServerRequest $request
* @param Response $response
* @param string $media_id
*
* @throws Exception\InvalidRequestAttribute
*/
public function submitAction(ServerRequest $request, Response $response, string $media_id): ResponseInterface
{
$station = $request->getStation();

View File

@ -16,39 +16,39 @@ use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Cache\CacheItem;
use Symfony\Contracts\Cache\CacheInterface;
/**
* @OA\Get(path="/station/{station_id}/schedule",
* operationId="getSchedule",
* tags={"Stations: Schedules"},
* description="Return upcoming and currently ongoing schedule entries.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="now",
* description="The date/time to compare schedule items to. Defaults to the current date and time.",
* in="query",
* required=false,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="rows",
* description="The number of upcoming/ongoing schedule entries to return. Defaults to 5.",
* in="query",
* required=false,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_StationSchedule"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied")
* )
*/
class ScheduleAction
{
use HasScheduleDisplay;
/**
* @OA\Get(path="/station/{station_id}/schedule",
* tags={"Stations: Schedules"},
* description="Return upcoming and currently ongoing schedule entries.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="now",
* description="The date/time to compare schedule items to. Defaults to the current date and time.",
* in="query",
* required=false,
* @OA\Schema(type="string")
* ),
* @OA\Parameter(
* name="rows",
* description="The number of upcoming/ongoing schedule entries to return. Defaults to 5.",
* in="query",
* required=false,
* @OA\Schema(type="integer")
* ),
* @OA\Response(
* response=200,
* description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/Api_StationSchedule"))
* ),
* @OA\Response(response=404, description="Station not found"),
* @OA\Response(response=403, description="Access denied")
* )
*/
public function __invoke(
ServerRequest $request,
Response $response,

View File

@ -15,6 +15,71 @@ use Doctrine\ORM\EntityManagerInterface;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/station/{station_id}/status",
* operationId="getServiceStatus",
* tags={"Stations: Service Control"},
* description="Retrieve the current status of all serivces associated with the radio broadcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(ref="#/components/schemas/Api_StationServiceStatus")
* ),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @OA\Post(path="/station/{station_id}/restart",
* operationId="restartServices",
* tags={"Stations: Service Control"},
* description="Restart all services associated with the radio broadcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @OA\Post(path="/station/{station_id}/frontend/{action}",
* operationId="doFrontendServiceAction",
* tags={"Stations: Service Control"},
* description="Perform service control actions on the radio frontend (Icecast, SHOUTcast, etc.)",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="action",
* description="The action to perform (start, stop, restart)",
* in="path",
* required=false,
* @OA\Schema(
* type="string",
* default="restart"
* )
* ),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @OA\Post(path="/station/{station_id}/backend/{action}",
* operationId="doBackendServiceAction",
* tags={"Stations: Service Control"},
* description="Perform service control actions on the radio backend (Liquidsoap)",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="action",
* description="The action to perform (for all: start, stop, restart; for Liquidsoap only: skip, disconnect)",
* in="path",
* required=false,
* @OA\Schema(
* type="string",
* default="restart"
* )
* ),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*/
class ServicesController
{
public function __construct(
@ -23,23 +88,6 @@ class ServicesController
) {
}
/**
* @OA\Get(path="/station/{station_id}/status",
* tags={"Stations: Service Control"},
* description="Retrieve the current status of all serivces associated with the radio broadcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(
* response=200,
* description="Success",
* @OA\Schema(ref="#/components/schemas/Api_StationServiceStatus")
* ),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @param ServerRequest $request
* @param Response $response
*/
public function statusAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();
@ -57,19 +105,6 @@ class ServicesController
);
}
/**
* @OA\Post(path="/station/{station_id}/restart",
* tags={"Stations: Service Control"},
* description="Restart all services associated with the radio broadcast.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @param ServerRequest $request
* @param Response $response
*/
public function restartAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();
@ -78,34 +113,10 @@ class ServicesController
return $response->withJson(new Entity\Api\Status(true, __('Station restarted.')));
}
/**
* @OA\Post(path="/station/{station_id}/frontend/{action}",
* tags={"Stations: Service Control"},
* description="Perform service control actions on the radio frontend (Icecast, SHOUTcast, etc.)",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="action",
* description="The action to perform (start, stop, restart)",
* in="path",
* required=false,
* @OA\Schema(
* type="string",
* default="restart"
* )
* ),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @param ServerRequest $request
* @param Response $response
* @param string $do
*/
public function frontendAction(
ServerRequest $request,
Response $response,
$do = 'restart'
string $do = 'restart'
): ResponseInterface {
$station = $request->getStation();
$frontend = $request->getStationFrontend();
@ -141,36 +152,11 @@ class ServicesController
}
}
/**
* @OA\Post(path="/station/{station_id}/backend/{action}",
* tags={"Stations: Service Control"},
* description="Perform service control actions on the radio backend (Liquidsoap)",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="action",
* description="The action to perform (for all: start, stop, restart; for Liquidsoap only: skip, disconnect)",
* in="path",
* required=false,
* @OA\Schema(
* type="string",
* default="restart"
* )
* ),
* @OA\Response(response=200, description="Success", @OA\Schema(ref="#/components/schemas/Api_Status")),
* @OA\Response(response=403, description="Access Forbidden", @OA\Schema(ref="#/components/schemas/Api_Error")),
* security={{"api_key": {}}}
* )
*
* @param ServerRequest $request
* @param Response $response
* @param AutoDJ $autodj
* @param string $do
*/
public function backendAction(
ServerRequest $request,
Response $response,
AutoDJ $autodj,
$do = 'restart'
string $do = 'restart'
): ResponseInterface {
$station = $request->getStation();
$backend = $request->getStationBackend();

View File

@ -8,94 +8,97 @@ use App\Entity;
use OpenApi\Annotations as OA;
/**
* @OA\Get(path="/station/{station_id}/sftp-users",
* operationId="getSftpUsers",
* tags={"Stations: SFTP Users"},
* description="List all current SFTP users.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/SftpUser"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/sftp-users",
* operationId="addSftpUser",
* tags={"Stations: SFTP Users"},
* description="Create a new SFTP user.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/sftp-user/{id}",
* operationId="getSftpUser",
* tags={"Stations: SFTP Users"},
* description="Retrieve details for a single SFTP user.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="SFTP User ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/sftp-user/{id}",
* operationId="editSftpUser",
* tags={"Stations: SFTP Users"},
* description="Update details of a single SFTP user.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/sftp-user/{id}",
* operationId="deleteSftpUser",
* tags={"Stations: SFTP Users"},
* description="Delete a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @extends AbstractStationApiCrudController<Entity\SftpUser>
*/
class SftpUsersController extends AbstractStationApiCrudController
{
protected string $entityClass = Entity\SftpUser::class;
protected string $resourceRouteName = 'api:stations:sftp-user';
/**
* @OA\Get(path="/station/{station_id}/sftp-users",
* tags={"Stations: SFTP Users"},
* description="List all current SFTP users.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/SftpUser"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/sftp-users",
* tags={"Stations: SFTP Users"},
* description="Create a new SFTP user.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/sftp-user/{id}",
* tags={"Stations: SFTP Users"},
* description="Retrieve details for a single SFTP user.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="SFTP User ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/sftp-user/{id}",
* tags={"Stations: SFTP Users"},
* description="Update details of a single SFTP user.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/SftpUser")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/sftp-user/{id}",
* tags={"Stations: SFTP Users"},
* description="Delete a single remote relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Remote Relay 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": {}}},
* )
*/
}

View File

@ -13,6 +13,93 @@ use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface;
/**
* @OA\Get(path="/station/{station_id}/streamers",
* operationId="getStreamers",
* tags={"Stations: Streamers/DJs"},
* description="List all current Streamer/DJ accounts for the specified station.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationStreamer"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/streamers",
* operationId="addStreamer",
* tags={"Stations: Streamers/DJs"},
* description="Create a new Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/streamer/{id}",
* operationId="getStreamer",
* tags={"Stations: Streamers/DJs"},
* description="Retrieve details for a single Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/streamer/{id}",
* operationId="editStreamer",
* tags={"Stations: Streamers/DJs"},
* description="Update details of a single Streamer/DJ account.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/streamer/{id}",
* operationId="deleteStreamer",
* tags={"Stations: Streamers/DJs"},
* description="Delete a single Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="StationStreamer 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": {}}},
* )
*
* @extends AbstractScheduledEntityController<Entity\StationStreamer>
*/
class StreamersController extends AbstractScheduledEntityController
@ -20,96 +107,6 @@ class StreamersController extends AbstractScheduledEntityController
protected string $entityClass = Entity\StationStreamer::class;
protected string $resourceRouteName = 'api:stations:streamer';
/**
* @OA\Get(path="/station/{station_id}/streamers",
* tags={"Stations: Streamers/DJs"},
* description="List all current Streamer/DJ accounts for the specified station.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationStreamer"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/streamers",
* tags={"Stations: Streamers/DJs"},
* description="Create a new Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/streamer/{id}",
* tags={"Stations: Streamers/DJs"},
* description="Retrieve details for a single Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/streamer/{id}",
* tags={"Stations: Streamers/DJs"},
* description="Update details of a single Streamer/DJ account.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationStreamer")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Streamer 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/streamer/{id}",
* tags={"Stations: Streamers/DJs"},
* description="Delete a single Streamer/DJ account.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="StationStreamer 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": {}}},
* )
*/
/**
* Controller used to respond to AJAX requests from the streamer "Schedule View".
*
* @param ServerRequest $request
* @param Response $response
*/
public function scheduleAction(ServerRequest $request, Response $response): ResponseInterface
{
$station = $request->getStation();

View File

@ -6,9 +6,97 @@ namespace App\Controller\Api\Stations;
use App\Entity;
use App\Http\ServerRequest;
use InvalidArgumentException;
use OpenApi\Annotations as OA;
/**
* @OA\Get(path="/station/{station_id}/webhooks",
* operationId="getWebhooks",
* tags={"Stations: Web Hooks"},
* description="List all current web hooks.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationWebhook"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/webhooks",
* operationId="addWebhook",
* tags={"Stations: Web Hooks"},
* description="Create a new web hook.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/webhook/{id}",
* operationId="getWebhook",
* tags={"Stations: Web Hooks"},
* description="Retrieve details for a single web hook.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/webhook/{id}",
* operationId="editWebhook",
* tags={"Stations: Web Hooks"},
* description="Update details of a single web hook.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/webhook/{id}",
* operationId="deleteWebhook",
* tags={"Stations: Web Hooks"},
* description="Delete a single web hook relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook 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": {}}},
* )
*
* @extends AbstractStationApiCrudController<Entity\StationWebhook>
*/
class WebhooksController extends AbstractStationApiCrudController
@ -16,94 +104,10 @@ class WebhooksController extends AbstractStationApiCrudController
protected string $entityClass = Entity\StationWebhook::class;
protected string $resourceRouteName = 'api:stations:webhook';
/**
* @OA\Get(path="/station/{station_id}/webhooks",
* tags={"Stations: Web Hooks"},
* description="List all current web hooks.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(type="array", @OA\Items(ref="#/components/schemas/StationWebhook"))
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Post(path="/station/{station_id}/webhooks",
* tags={"Stations: Web Hooks"},
* description="Create a new web hook.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Get(path="/station/{station_id}/webhook/{id}",
* tags={"Stations: Web Hooks"},
* description="Retrieve details for a single web hook.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook ID",
* required=true,
* @OA\Schema(type="integer", format="int64")
* ),
* @OA\Response(response=200, description="Success",
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Response(response=403, description="Access denied"),
* security={{"api_key": {}}},
* )
*
* @OA\Put(path="/station/{station_id}/webhook/{id}",
* tags={"Stations: Web Hooks"},
* description="Update details of a single web hook.",
* @OA\RequestBody(
* @OA\JsonContent(ref="#/components/schemas/StationWebhook")
* ),
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook 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": {}}},
* )
*
* @OA\Delete(path="/station/{station_id}/webhook/{id}",
* tags={"Stations: Web Hooks"},
* description="Delete a single web hook relay.",
* @OA\Parameter(ref="#/components/parameters/station_id_required"),
* @OA\Parameter(
* name="id",
* in="path",
* description="Web Hook 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": {}}},
* )
*/
protected function viewRecord(object $record, ServerRequest $request): mixed
{
if (!($record instanceof Entity\StationWebhook)) {
throw new \InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
throw new InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
}
$return = $this->toArray($record);

View File

@ -8,6 +8,7 @@ use App\Exception\NotFoundException;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
use RuntimeException;
trait HasLogViewer
{
@ -58,7 +59,7 @@ trait HasLogViewer
if ($log_visible_size > 0) {
$fp = fopen($log_path, 'rb');
if (false === $fp) {
throw new \RuntimeException(sprintf('Could not open file at path "%s".', $log_path));
throw new RuntimeException(sprintf('Could not open file at path "%s".', $log_path));
}
fseek($fp, -$log_visible_size, SEEK_END);

View File

@ -9,6 +9,7 @@ use App\Http\ServerRequest;
use App\Radio\AutoDJ\Scheduler;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use InvalidArgumentException;
trait HasScheduleDisplay
{
@ -21,7 +22,7 @@ trait HasScheduleDisplay
$startDate = CarbonImmutable::createFromFormat('Y-m-d', $startDateStr, $tz);
if (false === $startDate) {
throw new \InvalidArgumentException(sprintf('Could not parse start date: "%s"', $startDateStr));
throw new InvalidArgumentException(sprintf('Could not parse start date: "%s"', $startDateStr));
}
$startDate = $startDate->startOf('day');
@ -30,7 +31,7 @@ trait HasScheduleDisplay
$endDate = CarbonImmutable::createFromFormat('Y-m-d', $endDateStr, $tz);
if (false === $endDate) {
throw new \InvalidArgumentException(sprintf('Could not parse end date: "%s"', $endDateStr));
throw new InvalidArgumentException(sprintf('Could not parse end date: "%s"', $endDateStr));
}
$endDate = $endDate->endOf('day');

View File

@ -9,7 +9,9 @@ use App\Http\Response;
use App\Http\ServerRequest;
use App\Session\Flash;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class RecoverAction
{
@ -45,7 +47,7 @@ class RecoverAction
$csrf->verify($data['csrf'] ?? null, 'recover');
if (empty($data['password'])) {
throw new \InvalidArgumentException('Password required.');
throw new InvalidArgumentException('Password required.');
}
$user = $request->getUser();
@ -69,7 +71,7 @@ class RecoverAction
);
return $response->withRedirect((string)$request->getRouter()->named('dashboard'));
} catch (\Throwable $e) {
} catch (Throwable $e) {
$error = $e->getMessage();
}
}

View File

@ -14,8 +14,10 @@ use App\Session\Flash;
use App\Version;
use App\VueComponent\StationFormComponent;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Throwable;
class SetupController
{
@ -65,7 +67,7 @@ class SetupController
$csrf->verify($data['csrf'] ?? null, 'register');
if (empty($data['username']) || empty($data['password'])) {
throw new \InvalidArgumentException('Username and password required.');
throw new InvalidArgumentException('Username and password required.');
}
$role = $permissionRepo->ensureSuperAdministratorRole();
@ -92,7 +94,7 @@ class SetupController
$acl->reload();
return $response->withRedirect((string)$request->getRouter()->named('setup:index'));
} catch (\Throwable $e) {
} catch (Throwable $e) {
$error = $e->getMessage();
}
}

View File

@ -9,6 +9,7 @@ use Carbon\CarbonImmutable;
use DateTimeInterface;
use DateTimeZone;
use Doctrine\ORM\Mapping as ORM;
use RuntimeException;
#[
ORM\Entity(readOnly: true),
@ -98,7 +99,7 @@ class Analytics implements IdentifiableEntityInterface
public function getMomentInStationTimeZone(): CarbonImmutable
{
if (null === $this->station) {
throw new \RuntimeException('Cannot get moment in station timezone; no station associated.');
throw new RuntimeException('Cannot get moment in station timezone; no station associated.');
}
$tz = $this->station->getTimezoneObject();

View File

@ -6,6 +6,7 @@ namespace App\Entity\Api;
use App\Exception;
use OpenApi\Annotations as OA;
use ReflectionClass;
use Throwable;
/**
@ -104,7 +105,7 @@ class Error
$code = 500;
}
$className = (new \ReflectionClass($e))->getShortName();
$className = (new ReflectionClass($e))->getShortName();
$errorHeader = $className . ' at ' . $e->getFile() . ' L' . $e->getLine();
$message = $errorHeader . ': ' . $e->getMessage();

View File

@ -96,11 +96,7 @@ class NowPlayingApiGenerator
? $current_streamer->getDisplayName()
: 'Live DJ';
$broadcastStart = null;
$broadcast = $this->broadcastRepo->getLatestBroadcast($station);
if (null !== $broadcast) {
$broadcastStart = $broadcast->getTimestampStart();
}
$broadcastStart = $this->broadcastRepo->getLatestBroadcast($station)?->getTimestampStart();
$np->live = new Entity\Api\NowPlaying\Live(true, $streamer_name, $broadcastStart);
} else {

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Entity\ApiGenerator;
use App\Entity;
use App\Entity\Api\NowPlaying\SongHistory;
use Psr\Http\Message\UriInterface;
class SongHistoryApiGenerator
@ -20,8 +21,8 @@ class SongHistoryApiGenerator
Entity\SongHistory $record,
?UriInterface $baseUri = null,
bool $allowRemoteArt = false
): Entity\Api\NowPlaying\SongHistory {
$response = new Entity\Api\NowPlaying\SongHistory();
): SongHistory {
$response = new SongHistory();
$response->sh_id = $record->getIdRequired();
$response->played_at = (0 === $record->getTimestampStart())
? 0
@ -64,7 +65,7 @@ class SongHistoryApiGenerator
* @param UriInterface|null $baseUri
* @param bool $allowRemoteArt
*
* @return \App\Entity\Api\NowPlaying\SongHistory[]
* @return SongHistory[]
*/
public function fromArray(
array $records,

View File

@ -95,7 +95,7 @@ class PodcastMedia implements IdentifiableEntityInterface
public function setLength(float $length): self
{
$lengthMin = floor($length / 60);
$lengthSec = $length % 60;
$lengthSec = (int)$length % 60;
$this->length = $length;
$this->length_text = $lengthMin . ':' . str_pad((string)$lengthSec, 2, '0', STR_PAD_LEFT);

View File

@ -70,7 +70,7 @@ class ListenerRepository extends Repository
*/
public function update(Entity\Station $station, array $clients): void
{
$this->em->transactional(
$this->em->wrapInTransaction(
function () use ($station, $clients): void {
$existingClientsRaw = $this->em->createQuery(
<<<'DQL'
@ -102,18 +102,18 @@ class ListenerRepository extends Repository
} else {
// Create a new record.
$record = [
'station_id' => $station->getId(),
'timestamp_start' => time(),
'timestamp_end' => 0,
'listener_uid' => (int)$client->uid,
'station_id' => $station->getId(),
'timestamp_start' => time(),
'timestamp_end' => 0,
'listener_uid' => (int)$client->uid,
'listener_user_agent' => mb_substr(
$client->userAgent ?? '',
0,
255,
'UTF-8'
),
'listener_ip' => $client->ip,
'listener_hash' => Entity\Listener::calculateListenerHash($client),
'listener_ip' => $client->ip,
'listener_hash' => Entity\Listener::calculateListenerHash($client),
];
if (!empty($client->mount)) {

View File

@ -66,7 +66,7 @@ class StationMediaRepository extends Repository
$media = $this->repository->findOneBy(
[
'storage_location' => $storageLocation,
'id' => $id,
'id' => $id,
]
);
@ -86,7 +86,7 @@ class StationMediaRepository extends Repository
$media = $this->repository->findOneBy(
[
'storage_location' => $storageLocation,
'path' => $path,
'path' => $path,
]
);
@ -120,7 +120,7 @@ class StationMediaRepository extends Repository
$media = $this->repository->findOneBy(
[
'storage_location' => $storageLocation,
'unique_id' => $uniqueId,
'unique_id' => $uniqueId,
]
);
@ -382,12 +382,8 @@ class StationMediaRepository extends Repository
return $fs->withLocalFile(
$media->getPath(),
function ($path) use ($metadata) {
try {
$this->metadataManager->write($metadata, $path);
return true;
} catch (CannotProcessMediaException $e) {
throw $e;
}
$this->metadataManager->write($metadata, $path);
return true;
}
);
}

View File

@ -159,7 +159,7 @@ class StationPlaylistMediaRepository extends Repository
DQL
)->setParameter('playlist_id', $playlist->getId());
$this->em->transactional(
$this->em->wrapInTransaction(
function () use ($update_query, $mapping): void {
foreach ($mapping as $id => $weight) {
$update_query->setParameter('id', $id)

View File

@ -8,8 +8,6 @@ use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository;
use App\Entity;
use App\Environment;
use App\Flysystem\StationFilesystems;
use App\Radio\Adapters;
use App\Radio\AutoDJ\Scheduler;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
@ -104,10 +102,6 @@ class StationStreamerRepository extends Repository
public function onDisconnect(Entity\Station $station): bool
{
$fs = new StationFilesystems($station);
$fsTemp = $fs->getTempFilesystem();
$fsRecordings = $fs->getRecordingsFilesystem();
foreach ($this->broadcastRepo->getActiveBroadcasts($station) as $broadcast) {
$broadcast->setTimestampEnd(time());
$this->em->persist($broadcast);

View File

@ -14,6 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
use GuzzleHttp\Psr7\Uri;
use OpenApi\Annotations as OA;
use Psr\Http\Message\UriInterface;
use RuntimeException;
use Stringable;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
@ -47,7 +48,7 @@ class Settings implements Stringable
public function getAppUniqueIdentifier(): string
{
if (!isset($this->app_unique_identifier)) {
throw new \RuntimeException('Application Unique ID not generated yet.');
throw new RuntimeException('Application Unique ID not generated yet.');
}
return $this->app_unique_identifier;

View File

@ -16,6 +16,7 @@ use DateTimeZone;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use OpenApi\Annotations as OA;
use Psr\Http\Message\UriInterface;
@ -925,12 +926,12 @@ class Station implements Stringable, IdentifiableEntityInterface
{
$supportedTypes = self::getStorageLocationTypes();
if (!isset($supportedTypes[$type])) {
throw new \InvalidArgumentException(sprintf('Invalid type: %s', $type));
throw new InvalidArgumentException(sprintf('Invalid type: %s', $type));
}
$record = $this->{$supportedTypes[$type]};
if (null === $record) {
throw new \RuntimeException(sprintf('Storage location for type %s has not been configured yet.', $type));
throw new RuntimeException(sprintf('Storage location for type %s has not been configured yet.', $type));
}
return $record;
}
@ -943,12 +944,12 @@ class Station implements Stringable, IdentifiableEntityInterface
}
if ($type !== $newStorageLocation->getType()) {
throw new \InvalidArgumentException(sprintf('Specified location is not of type %s.', $type));
throw new InvalidArgumentException(sprintf('Specified location is not of type %s.', $type));
}
$supportedTypes = self::getStorageLocationTypes();
if (!isset($supportedTypes[$type])) {
throw new \InvalidArgumentException(sprintf('Invalid type: %s', $type));
throw new InvalidArgumentException(sprintf('Invalid type: %s', $type));
}
$this->{$supportedTypes[$type]} = $newStorageLocation;

View File

@ -84,11 +84,11 @@ class StationPlaylistMedia implements JsonSerializable, IdentifiableEntityInterf
/**
* @inheritDoc
*/
public function jsonSerialize()
public function jsonSerialize(): array
{
return [
'id' => $this->playlist->getId(),
'name' => $this->playlist->getName(),
'id' => $this->playlist->getId(),
'name' => $this->playlist->getName(),
'weight' => $this->weight,
];
}

View File

@ -22,11 +22,11 @@ class StationSchedule implements IdentifiableEntityInterface
{
use Traits\HasAutoIncrementId;
#[ORM\ManyToOne(inversedBy: 'schedules')]
#[ORM\ManyToOne(inversedBy: 'schedule_items')]
#[ORM\JoinColumn(name: 'playlist_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
protected ?StationPlaylist $playlist = null;
#[ORM\ManyToOne(inversedBy: 'schedules')]
#[ORM\ManyToOne(inversedBy: 'schedule_items')]
#[ORM\JoinColumn(name: 'streamer_id', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
protected ?StationStreamer $streamer = null;

View File

@ -22,6 +22,7 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
use RuntimeException;
use Spatie\Dropbox\Client;
use Stringable;
use Symfony\Component\Validator\Constraints as Assert;
@ -443,7 +444,7 @@ class StorageLocation implements Stringable, IdentifiableEntityInterface
case self::ADAPTER_S3:
$bucket = $this->s3Bucket;
if (null === $bucket) {
throw new \RuntimeException('Amazon S3 bucket is empty.');
throw new RuntimeException('Amazon S3 bucket is empty.');
}
return new AwsS3Adapter($this->getS3Client(), $bucket, $filteredPath);

View File

@ -7,6 +7,7 @@ namespace App\Entity\Traits;
use App\Entity\Interfaces\EntityGroupsInterface;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Annotations as OA;
use RuntimeException;
use Symfony\Component\Serializer\Annotation\Groups;
/**
@ -28,7 +29,7 @@ trait HasAutoIncrementId
public function getIdRequired(): int
{
if (null === $this->id) {
throw new \RuntimeException('An ID was not generated for this object.');
throw new RuntimeException('An ID was not generated for this object.');
}
return $this->id;

View File

@ -8,6 +8,7 @@ use App\Doctrine\Generator\UuidV6Generator;
use App\Entity\Interfaces\EntityGroupsInterface;
use Doctrine\ORM\Mapping as ORM;
use OpenApi\Annotations as OA;
use RuntimeException;
use Symfony\Component\Serializer\Annotation\Groups;
/**
@ -29,7 +30,7 @@ trait HasUniqueId
public function getIdRequired(): string
{
if (null === $this->id) {
throw new \RuntimeException('An ID was not generated for this object.');
throw new RuntimeException('An ID was not generated for this object.');
}
return $this->id;

View File

@ -23,7 +23,7 @@ final class Response extends \Slim\Http\Response
* Send headers that expire the content immediately and prevent caching.
* @return static
*/
public function withNoCache(): static
public function withNoCache(): Response
{
$response = $this->response
->withHeader('Pragma', 'no-cache')
@ -31,7 +31,7 @@ final class Response extends \Slim\Http\Response
->withHeader('Cache-Control', 'private, no-cache, no-store')
->withHeader('X-Accel-Expires', '0'); // CloudFlare
return new static($response, $this->streamFactory);
return new Response($response, $this->streamFactory);
}
/**
@ -41,7 +41,7 @@ final class Response extends \Slim\Http\Response
*
* @return static
*/
public function withCacheLifetime(int $seconds = self::CACHE_ONE_MONTH): static
public function withCacheLifetime(int $seconds = self::CACHE_ONE_MONTH): Response
{
$response = $this->response
->withHeader('Pragma', '')
@ -49,7 +49,7 @@ final class Response extends \Slim\Http\Response
->withHeader('Cache-Control', 'public, must-revalidate, max-age=' . $seconds)
->withHeader('X-Accel-Expires', (string)$seconds); // CloudFlare
return new static($response, $this->streamFactory);
return new Response($response, $this->streamFactory);
}
/**
@ -88,7 +88,7 @@ final class Response extends \Slim\Http\Response
*
* @return static
*/
public function renderFile(string $file_path, $file_name = null): static
public function renderFile(string $file_path, $file_name = null): Response
{
set_time_limit(600);
@ -107,7 +107,7 @@ final class Response extends \Slim\Http\Response
->withHeader('Content-Disposition', 'attachment; filename=' . $file_name)
->withBody($stream);
return new static($response, $this->streamFactory);
return new Response($response, $this->streamFactory);
}
/**
@ -119,7 +119,7 @@ final class Response extends \Slim\Http\Response
*
* @return static
*/
public function renderStringAsFile(string $file_data, string $content_type, ?string $file_name = null): static
public function renderStringAsFile(string $file_data, string $content_type, ?string $file_name = null): Response
{
$response = $this->response
->withHeader('Pragma', 'public')
@ -133,7 +133,7 @@ final class Response extends \Slim\Http\Response
$response->getBody()->write($file_data);
return new static($response, $this->streamFactory);
return new Response($response, $this->streamFactory);
}
/**
@ -149,7 +149,7 @@ final class Response extends \Slim\Http\Response
StreamInterface $fileStream,
string $contentType,
?string $fileName = null
): static {
): Response {
set_time_limit(600);
$response = $this->response
@ -164,7 +164,7 @@ final class Response extends \Slim\Http\Response
$response = $response->withBody($fileStream);
return new static($response, $this->streamFactory);
return new Response($response, $this->streamFactory);
}
public function streamFilesystemFile(

View File

@ -15,6 +15,7 @@ use App\RateLimit;
use App\Session;
use App\View;
use Mezzio\Session\SessionInterface;
use RuntimeException;
final class ServerRequest extends \Slim\Http\ServerRequest
{
@ -170,7 +171,7 @@ final class ServerRequest extends \Slim\Http\ServerRequest
?? null;
if (null === $ip) {
throw new \RuntimeException('No IP address attached to this request.');
throw new RuntimeException('No IP address attached to this request.');
}
// Handle the IP being separated by commas.

View File

@ -14,6 +14,7 @@ use Azura\MetadataManager\Metadata;
use Azura\MetadataManager\MetadataInterface;
use GuzzleHttp\Client;
use Psr\EventDispatcher\EventDispatcherInterface;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Process;
@ -65,7 +66,7 @@ class MetadataManager implements EventSubscriberInterface
try {
$phpBinaryPath = (new PhpExecutableFinder())->find();
if (false === $phpBinaryPath) {
throw new \RuntimeException('Could not find PHP executable path.');
throw new RuntimeException('Could not find PHP executable path.');
}
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';
@ -134,7 +135,7 @@ class MetadataManager implements EventSubscriberInterface
// Run remote process.
$phpBinaryPath = (new PhpExecutableFinder())->find();
if (false === $phpBinaryPath) {
throw new \RuntimeException('Could not find PHP executable path.');
throw new RuntimeException('Could not find PHP executable path.');
}
$scriptPath = $this->environment->getBaseDirectory() . '/vendor/bin/metadata-manager';

View File

@ -6,6 +6,7 @@ namespace App\Message;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\MessageQueue\QueueManager;
use App\MessageQueue\QueueManagerInterface;
class DispatchWebhookMessage extends AbstractUniqueMessage
{
@ -23,6 +24,6 @@ class DispatchWebhookMessage extends AbstractUniqueMessage
public function getQueue(): string
{
return QueueManager::QUEUE_HIGH_PRIORITY;
return QueueManagerInterface::QUEUE_HIGH_PRIORITY;
}
}

View File

@ -11,7 +11,7 @@ class TestQueueManager extends AbstractQueueManager
{
public function clearQueue(string $queueName): void
{
return; // Noop
// Noop
}
public function getTransport(string $queueName): TransportInterface

View File

@ -152,7 +152,7 @@ class AutoDJ
);
} else {
// Prevent the exact same track from being played twice during this loop
if (null !== $lastSongId && $lastSongId === $queueRow->getSongId()) {
if ($lastSongId === $queueRow->getSongId()) {
$this->em->remove($queueRow);
continue;
}
@ -179,7 +179,7 @@ class AutoDJ
$this->em->persist($queueRow);
// Prevent the exact same track from being played twice during this loop
if (null !== $lastSongId && $lastSongId === $queueRow->getSongId()) {
if ($lastSongId === $queueRow->getSongId()) {
$this->em->remove($queueRow);
} else {
$lastSongId = $queueRow->getSongId();

View File

@ -50,7 +50,7 @@ class Flow
if (null === $tempDir) {
$tempDir = sys_get_temp_dir() . '/uploads';
if (!mkdir($tempDir) && !is_dir($tempDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $tempDir));
throw new RuntimeException(sprintf('Directory "%s" was not created', $tempDir));
}
}
@ -195,7 +195,7 @@ class Flow
$fp = fopen($finalPath, 'wb+');
if (false === $fp) {
throw new \RuntimeException(
throw new RuntimeException(
sprintf(
'Could not open final path "%s" for writing.',
$finalPath
@ -206,7 +206,7 @@ class Flow
for ($i = 1; $i <= $numChunks; $i++) {
$chunkContents = file_get_contents($chunkBaseDir . '/' . $chunkIdentifier . '.part' . $i);
if (empty($chunkContents)) {
throw new \RuntimeException(
throw new RuntimeException(
sprintf(
'Could not load chunk "%d" for writing.',
$i
@ -248,7 +248,6 @@ class Flow
unlink($dir . '/' . $object);
}
}
reset($objects);
rmdir($dir);
}
}

View File

@ -6,11 +6,14 @@ namespace App\Service\Flow;
use App\Utilities\File;
use GuzzleHttp\Psr7\LazyOpenStream;
use InvalidArgumentException;
use JsonSerializable;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use League\MimeTypeDetection\GeneratedExtensionToMimeTypeMap;
use Normalizer;
use Psr\Http\Message\StreamInterface;
use Psr\Http\Message\UploadedFileInterface;
use RuntimeException;
final class UploadedFile implements UploadedFileInterface, JsonSerializable
{
@ -29,7 +32,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
$clientFilename ??= tempnam($tempDir, 'upload');
if (!$clientFilename || !$tempDir) {
throw new \RuntimeException('Could not generate original filename.');
throw new RuntimeException('Could not generate original filename.');
}
$clientFilename = self::filterOriginalFilename($clientFilename);
@ -41,14 +44,14 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
} else {
$uploadedPath = realpath($uploadedPath);
if (false === $uploadedPath) {
throw new \InvalidArgumentException('Could not determine real path of specified path.');
throw new InvalidArgumentException('Could not determine real path of specified path.');
}
if (!str_starts_with($uploadedPath, $tempDir)) {
throw new \InvalidArgumentException('Uploaded path is not inside specified temporary directory.');
throw new InvalidArgumentException('Uploaded path is not inside specified temporary directory.');
}
if (!is_file($uploadedPath)) {
throw new \InvalidArgumentException(sprintf('File does not exist at path: %s', $uploadedPath));
throw new InvalidArgumentException(sprintf('File does not exist at path: %s', $uploadedPath));
}
$this->file = $uploadedPath;
@ -71,7 +74,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
$size = filesize($this->file);
if (false === $size) {
throw new \RuntimeException('Could not get file size of uploaded path.');
throw new RuntimeException('Could not get file size of uploaded path.');
}
return $size;
@ -119,7 +122,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
$this->moved = rename($this->file, $targetPath);
if (false === $this->moved) {
throw new \RuntimeException(
throw new RuntimeException(
sprintf('Uploaded file could not be moved to %s', $targetPath)
);
}
@ -133,7 +136,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
private function validateActive(): void
{
if ($this->moved) {
throw new \RuntimeException('Cannot retrieve stream after it has already been moved');
throw new RuntimeException('Cannot retrieve stream after it has already been moved');
}
}
@ -149,7 +152,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
public static function fromArray(array $input, string $tempDir): self
{
if (!isset($input['originalFilename'], $input['uploadedPath'])) {
throw new \InvalidArgumentException('Uploaded file array is malformed.');
throw new InvalidArgumentException('Uploaded file array is malformed.');
}
return new self($input['originalFilename'], $input['uploadedPath'], $tempDir);
@ -158,7 +161,7 @@ final class UploadedFile implements UploadedFileInterface, JsonSerializable
public static function filterOriginalFilename(string $name): string
{
$name = basename($name);
$normalizedName = \Normalizer::normalize($name, \Normalizer::FORM_KD);
$normalizedName = Normalizer::normalize($name, Normalizer::FORM_KD);
if (false !== $normalizedName) {
$name = $normalizedName;
}

View File

@ -62,7 +62,7 @@ class CheckFolderPlaylistsTask extends AbstractTask
continue;
}
$this->em->transactional(
$this->em->wrapInTransaction(
function () use ($station, $playlist, $fsMedia, $mediaInPlaylistQuery, $mediaInFolderQuery): void {
$this->processPlaylist(
$station,

View File

@ -12,6 +12,7 @@ use App\MessageQueue\QueueManagerInterface;
use App\Radio\Quota;
use Azura\Files\Attributes\FileAttributes;
use Brick\Math\BigInteger;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Query;
use League\Flysystem\FilesystemException;
use League\Flysystem\StorageAttributes;
@ -182,7 +183,7 @@ class CheckMediaTask extends AbstractTask
DQL
)->setParameter('storageLocation', $storageLocation);
foreach ($existingMediaQuery->toIterable([], Query::HYDRATE_ARRAY) as $mediaRow) {
foreach ($existingMediaQuery->toIterable([], AbstractQuery::HYDRATE_ARRAY) as $mediaRow) {
// Check if media file still exists.
$path = $mediaRow['path'];
$pathHash = md5($path);
@ -236,7 +237,7 @@ class CheckMediaTask extends AbstractTask
DQL
)->setParameter('storageLocation', $storageLocation);
$unprocessableRecords = $unprocessableMediaQuery->toIterable([], Query::HYDRATE_ARRAY);
$unprocessableRecords = $unprocessableMediaQuery->toIterable([], AbstractQuery::HYDRATE_ARRAY);
foreach ($unprocessableRecords as $unprocessableRow) {
$pathHash = md5($unprocessableRow['path']);

View File

@ -6,6 +6,7 @@ namespace App\Sync\Task;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\Environment;
use App\Event\Radio\GenerateRawNowPlaying;
use App\Http\RouterInterface;
@ -75,7 +76,7 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
/**
* @param bool $force
*
* @return Entity\Api\NowPlaying\NowPlaying[]
* @return NowPlaying[]
*/
protected function loadNowPlaying(bool $force = false): array
{
@ -99,7 +100,7 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
Entity\Station $station,
bool $standalone = false,
bool $force = false
): Entity\Api\NowPlaying\NowPlaying {
): NowPlaying {
$lock = $this->lockFactory->createAndAcquireLock(
resource: 'nowplaying_station_' . $station->getId(),
ttl: 600,
@ -294,10 +295,10 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
protected function dispatchWebhooks(
Entity\Station $station,
Entity\Api\NowPlaying\NowPlaying $npOriginal,
NowPlaying $npOriginal,
bool $isStandalone = true
): void {
/** @var \App\Entity\Api\NowPlaying\NowPlaying $np */
/** @var NowPlaying $np */
$np = (new DeepCopy())->copy($npOriginal);
$np->resolveUrls($this->router->getBaseUrl());
$np->cache = 'event';
@ -307,7 +308,7 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
Entity\StationWebhook::TRIGGER_ALL,
];
if ($npOld instanceof Entity\Api\NowPlaying\NowPlaying) {
if ($npOld instanceof NowPlaying) {
if ($npOld->now_playing?->song?->id !== $np->now_playing?->song?->id) {
$triggers[] = Entity\StationWebhook::TRIGGER_SONG_CHANGED;
}
@ -341,7 +342,7 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
protected function updateCaches(
Entity\Station $station,
Entity\Api\NowPlaying\NowPlaying $np
NowPlaying $np
): void {
// Replace the relevant station information in the cache and database.
$this->logger->debug('Updating NowPlaying cache...');
@ -351,7 +352,7 @@ class NowPlayingTask extends AbstractTask implements EventSubscriberInterface
if ($np_full) {
$np_new = [];
foreach ($np_full as $np_old) {
/** @var \App\Entity\Api\NowPlaying\NowPlaying $np_old */
/** @var NowPlaying $np_old */
if ($np_old->station->id === $station->getId()) {
$np_new[] = $np;
} else {

View File

@ -61,7 +61,7 @@ class RunAnalyticsTask extends AbstractTask
$this->analyticsRepo->cleanup();
while ($day < $now) {
$this->em->transactional(
$this->em->wrapInTransaction(
function () use ($day, $stations, $withListeners): void {
$this->processDay($day, $stations, $withListeners);
}

View File

@ -17,6 +17,7 @@ use Codeception\Lib\ModuleContainer;
use Codeception\TestInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Container\ContainerInterface;
use RuntimeException;
use Slim\App;
class Module extends Framework implements DoctrineProvider
@ -51,7 +52,7 @@ class Module extends Framework implements DoctrineProvider
$container = $this->app->getContainer();
if (null === $container) {
throw new \RuntimeException('Container was not set on App.');
throw new RuntimeException('Container was not set on App.');
}
$this->container = $container;

View File

@ -4,9 +4,11 @@ declare(strict_types=1);
namespace App\Utilities;
use FilesystemIterator;
use InvalidArgumentException;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use SplFileInfo;
use function stripos;
@ -41,12 +43,12 @@ class File
{
$str = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $str);
if (null === $str || false === $str) {
throw new \RuntimeException('Cannot parse input string.');
throw new RuntimeException('Cannot parse input string.');
}
$str = mb_ereg_replace("([\.]{2,})", '.', $str);
if (null === $str || false === $str) {
throw new \RuntimeException('Cannot parse input string.');
throw new RuntimeException('Cannot parse input string.');
}
$str = str_replace(' ', '_', $str);
@ -98,7 +100,7 @@ class File
}
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
new RecursiveDirectoryIterator($source, FilesystemIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);

View File

@ -4,6 +4,9 @@ declare(strict_types=1);
namespace App\Utilities;
use JsonException;
use RuntimeException;
class Json
{
public static function loadFromFile(
@ -15,9 +18,9 @@ class Json
if (false !== $fileContents) {
try {
return (array)json_decode($fileContents, true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
} catch (JsonException $e) {
if ($throwOnError) {
throw new \RuntimeException(
throw new RuntimeException(
sprintf(
'Could not parse JSON at "%s": %s',
$path,
@ -27,10 +30,10 @@ class Json
}
}
} elseif ($throwOnError) {
throw new \RuntimeException(sprintf('Error reading file: "%s"', $path));
throw new RuntimeException(sprintf('Error reading file: "%s"', $path));
}
} elseif ($throwOnError) {
throw new \RuntimeException(sprintf('File not found: "%s"', $path));
throw new RuntimeException(sprintf('File not found: "%s"', $path));
}
return [];

View File

@ -34,7 +34,7 @@ else
fi
APP_ENV="${APP_ENV:-production}"
UPDATE_REVISION="${UPDATE_REVISION:-67}"
UPDATE_REVISION="${UPDATE_REVISION:-68}"
echo "Updating AzuraCast (Environment: $APP_ENV, Update revision: $UPDATE_REVISION)"

View File

@ -45,6 +45,7 @@
- gzip
- zip
- unzip
- zstd
- name: Install Focal Packages (20.04)
apt:

View File

@ -57,33 +57,46 @@
- php7.4-bcmath
- php7.4-gmp
- php7.4-maxminddb
- php8.0-fpm
- php8.0-cli
- php8.0-gd
- php8.0-curl
- php8.0-xml
- php8.0-zip
- php8.0-mysqlnd
- php8.0-mbstring
- php8.0-intl
- php8.0-redis
- php8.0-bcmath
- php8.0-gmp
- php8.0-maxminddb
- name : Install PHP 8.0
- name : Install PHP 8.1
apt :
name : "{{ packages }}"
state : latest
vars :
packages :
- php8.0-fpm
- php8.0-cli
- php8.0-gd
- php8.0-curl
- php8.0-xml # IceCast XML config
- php8.0-zip # Composer installs
- php8.0-mysqlnd # MySQL Native Driver (Doctrine)
- php8.0-mbstring # Codeception Tests
- php8.0-intl # Localization
- php8.0-redis # Cache
- php8.0-bcmath # BigInteger
- php8.0-gmp # BigInteger and PHP-IP
- php8.0-maxminddb # Maxmind (GeoIP) DB native ext
- php8.1-fpm
- php8.1-cli
- php8.1-gd
- php8.1-curl
- php8.1-xml # IceCast XML config
- php8.1-zip # Composer installs
- php8.1-mysqlnd # MySQL Native Driver (Doctrine)
- php8.1-mbstring # Codeception Tests
- php8.1-intl # Localization
- php8.1-redis # Cache
- php8.1-bcmath # BigInteger
- php8.1-gmp # BigInteger and PHP-IP
- php8.1-maxminddb # Maxmind (GeoIP) DB native ext
- name: Configure PHP FPM Pool
template : src=fpmpool.j2 dest=/etc/php/8.0/fpm/pool.d/www.conf force=true
template : src=fpmpool.j2 dest=/etc/php/8.1/fpm/pool.d/www.conf force=true
- name: Configure php-fpm php.ini
ini_file:
dest : /etc/php/8.0/fpm/php.ini
dest : /etc/php/8.1/fpm/php.ini
section: PHP
option: "{{ item.option }}"
value: "{{ item.value }}"
@ -94,7 +107,7 @@
- name: Configure php-cli php.ini
ini_file:
dest : /etc/php/8.0/cli/php.ini
dest : /etc/php/8.1/cli/php.ini
section: PHP
option: "{{ item.option }}"
value: "{{ item.value }}"

View File

@ -22,7 +22,7 @@
- { role : nginx, when : update_revision|int < 60 }
- { role : redis, when : update_revision|int < 57 }
- { role: beanstalkd, when: update_revision|int < 67 }
- { role : php, when : update_revision|int < 62 }
- { role : php, when : update_revision|int < 68 }
- composer
- { role : influxdb, when : update_revision|int < 58 }
- { role : ufw, when : update_revision|int < 12 }

View File

@ -5,4 +5,7 @@ set -x
apt-get update
# Prevent systemd auto-startup
ln -s /dev/null /etc/systemd/system/beanstalkd.service
$minimal_apt_get_install beanstalkd

View File

@ -3,9 +3,10 @@ set -e
source /bd_build/buildconfig
set -x
$minimal_apt_get_install cron
# Prevent systemd auto-startup
ln -s /dev/null /etc/systemd/system/cron.service
service cron stop
$minimal_apt_get_install cron
chmod 600 /etc/crontab
@ -21,4 +22,4 @@ rm -f /etc/cron.daily/password
rm -f /etc/cron.weekly/fstrim
cp -r /bd_build/cron/. /etc/cron.d/
chmod -R 600 /etc/cron.d/*
chmod -R 600 /etc/cron.d/*

View File

@ -3,7 +3,7 @@ set -e
source /bd_build/buildconfig
set -x
PHP_VERSION=8.0
PHP_VERSION=8.1
add-apt-repository -y ppa:ondrej/php
apt-get update

View File

@ -0,0 +1,6 @@
#!/bin/bash
set -e
source /bd_build/buildconfig
set -x
$minimal_apt_get_install zstd

View File

@ -2,80 +2,88 @@
use OpenApi\Annotations as OA;
/**
* @OA\Info(
* version=AZURACAST_VERSION,
* title="AzuraCast",
* description="AzuraCast is a standalone, turnkey web radio management tool. Radio stations hosted by AzuraCast expose a public API for viewing now playing data, making requests and more.",
* @OA\License(
* name="Apache 2.0",
* url="http://www.apache.org/licenses/LICENSE-2.0.html"
* )
* )
*
* @OA\Server(
* description=AZURACAST_API_NAME,
* url=AZURACAST_API_URL
* )
*
* @OA\ExternalDocumentation(
* description="AzuraCast on GitHub",
* url="https://github.com/AzuraCast/AzuraCast"
* )
*
* @OA\Parameter(
* parameter="station_id_required",
* name="station_id",
* description="The station ID",
* example=1,
* in="path",
* required=true,
* @OA\Schema(
* anyOf={@OA\Schema(type="integer", format="int64"), @OA\Schema(type="string", format="string")}
* )
* )
*
* @OA\Response(
* response="todo",
* description="This API call has no documented response (yet)",
* )
*
* @OA\Tag(
* name="Now Playing",
* description="Endpoints that provide full summaries of the current state of stations.",
* )
*
* @OA\Tag(name="Stations: General")
* @OA\Tag(name="Stations: Song Requests")
* @OA\Tag(name="Stations: Service Control")
*
* @OA\Tag(name="Stations: History")
* @OA\Tag(name="Stations: Listeners")
* @OA\Tag(name="Stations: Schedules")
* @OA\Tag(name="Stations: Media")
* @OA\Tag(name="Stations: Mount Points")
* @OA\Tag(name="Stations: Playlists")
* @OA\Tag(name="Stations: Podcasts")
* @OA\Tag(name="Stations: Queue")
* @OA\Tag(name="Stations: Remote Relays")
* @OA\Tag(name="Stations: SFTP Users")
* @OA\Tag(name="Stations: Streamers/DJs")
* @OA\Tag(name="Stations: Web Hooks")
*
* @OA\Tag(name="Administration: Custom Fields")
* @OA\Tag(name="Administration: Users")
* @OA\Tag(name="Administration: Relays")
* @OA\Tag(name="Administration: Roles")
* @OA\Tag(name="Administration: Settings")
* @OA\Tag(name="Administration: Stations")
* @OA\Tag(name="Administration: Storage Locations")
*
* @OA\Tag(name="Miscellaneous")
*
* @OA\SecurityScheme(
* type="apiKey",
* in="header",
* securityScheme="api_key",
* name="X-API-Key"
* )
*/
#[
OA\OpenApi(
info: new OA\Info(
version: AZURACAST_VERSION,
title: 'AzuraCast',
description: "AzuraCast is a standalone, turnkey web radio management tool. Radio stations hosted by AzuraCast expose a public API for viewing now playing data, making requests and more.",
license: new OA\License(
name: 'Apache 2.0',
url: "http://www.apache.org/licenses/LICENSE-2.0.html"
),
),
servers: [
new OA\Server(
description: AZURACAST_API_NAME,
url: AZURACAST_API_URL
),
],
externalDocs: new OA\ExternalDocumentation(
description: "AzuraCast on GitHub",
url: "https://github.com/AzuraCast/AzuraCast"
),
tags: [
new OA\Tag(
name: "Now Playing",
description: "Endpoints that provide full summaries of the current state of stations.",
),
new OA\Tag(name: "Stations: General"),
new OA\Tag(name: "Stations: Song Requests"),
new OA\Tag(name: "Stations: Service Control"),
new OA\Tag(name: "Stations: History"),
new OA\Tag(name: "Stations: Listeners"),
new OA\Tag(name: "Stations: Schedules"),
new OA\Tag(name: "Stations: Media"),
new OA\Tag(name: "Stations: Mount Points"),
new OA\Tag(name: "Stations: Playlists"),
new OA\Tag(name: "Stations: Podcasts"),
new OA\Tag(name: "Stations: Queue"),
new OA\Tag(name: "Stations: Remote Relays"),
new OA\Tag(name: "Stations: SFTP Users"),
new OA\Tag(name: "Stations: Streamers/DJs"),
new OA\Tag(name: "Stations: Web Hooks"),
new OA\Tag(name: "Administration: Custom Fields"),
new OA\Tag(name: "Administration: Users"),
new OA\Tag(name: "Administration: Relays"),
new OA\Tag(name: "Administration: Roles"),
new OA\Tag(name: "Administration: Settings"),
new OA\Tag(name: "Administration: Stations"),
new OA\Tag(name: "Administration: Storage Locations"),
new OA\Tag(name: "Miscellaneous"),
]
),
OA\Parameter(
name: "station_id_required",
in: "path",
required: true,
schema: new OA\Schema(
properties: [
'anyOf' => [
new OA\Schema(type: "integer", format: "int64"),
new OA\Schema(type: "string", format: "string")
]
]
),
),
OA\Response(
response: "todo",
description: "This API call has no documented response (yet)",
),
OA\SecurityScheme(
type: "apiKey",
in: "header",
securityScheme: "api_key",
name: "X-API-Key"
)
]
class OpenApi
{
}

File diff suppressed because it is too large Load Diff