Unify feature checks; simplify station routes.

This commit is contained in:
Buster "Silver Eagle" Neece 2022-06-29 23:38:46 -05:00
parent 6529a92e58
commit 6e9af6f1f4
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
15 changed files with 666 additions and 426 deletions

View File

@ -3,6 +3,7 @@
* Administrative dashboard configuration.
*/
use App\Enums\StationFeatures;
use App\Enums\StationPermissions;
use App\Radio\Enums\AudioProcessingMethods;
@ -13,8 +14,6 @@ return function (App\Event\BuildStationMenu $e) {
$backendConfig = $station->getBackendConfig();
$router = $request->getRouter();
$backendEnum = $station->getBackendTypeEnum();
$frontendEnum = $station->getFrontendTypeEnum();
$willDisconnectMessage = __('Restart broadcasting? This will disconnect any current listeners.');
@ -63,33 +62,30 @@ return function (App\Event\BuildStationMenu $e) {
'media' => [
'label' => __('Media'),
'icon' => 'library_music',
'visible' => StationFeatures::Media->supportedForStation($station),
'items' => [
'files' => [
'label' => __('Music Files'),
'icon' => 'library_music',
'url' => (string)$router->fromHere('stations:files:index'),
'visible' => $backendEnum->isEnabled(),
'permission' => StationPermissions::Media,
],
'reports_duplicates' => [
'label' => __('Duplicate Songs'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:files:index') . '#special:duplicates',
'visible' => $backendEnum->isEnabled(),
'permission' => StationPermissions::Media,
],
'reports_unprocessable' => [
'label' => __('Unprocessable Files'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:files:index') . '#special:unprocessable',
'visible' => $backendEnum->isEnabled(),
'permission' => StationPermissions::Media,
],
'reports_unassigned' => [
'label' => __('Unassigned Files'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:files:index') . '#special:unassigned',
'visible' => $backendEnum->isEnabled(),
'permission' => StationPermissions::Media,
],
'ondemand' => [
@ -111,7 +107,6 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Bulk Media Import/Export'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:bulk-media'),
'visible' => $backendEnum->isEnabled(),
'permission' => StationPermissions::Media,
],
],
@ -121,7 +116,7 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Playlists'),
'icon' => 'queue_music',
'url' => (string)$router->fromHere('stations:playlists:index'),
'visible' => $backendEnum->isEnabled(),
'visible' => StationFeatures::Media->supportedForStation($station),
'permission' => StationPermissions::Media,
],
@ -129,18 +124,19 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Podcasts'),
'icon' => 'cast',
'url' => (string)$router->fromHere('stations:podcasts:index'),
'visible' => StationFeatures::Podcasts->supportedForStation($station),
'permission' => StationPermissions::Podcasts,
],
'live_streaming' => [
'label' => __('Live Streaming'),
'icon' => 'mic',
'visible' => StationFeatures::Streamers->supportedForStation($station),
'items' => [
'streamers' => [
'label' => __('Streamer/DJ Accounts'),
'icon' => 'mic',
'url' => (string)$router->fromHere('stations:streamers:index'),
'visible' => $backendEnum->isEnabled() && $station->getEnableStreamers(),
'permission' => StationPermissions::Streamers,
],
@ -154,7 +150,7 @@ return function (App\Event\BuildStationMenu $e) {
true
)
->withScheme('https'),
'visible' => $station->getEnablePublicPage() && $station->getEnableStreamers(),
'visible' => $station->getEnablePublicPage(),
'external' => true,
],
],
@ -164,6 +160,7 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Web Hooks'),
'icon' => 'code',
'url' => (string)$router->fromHere('stations:webhooks:index'),
'visible' => StationFeatures::Webhooks->supportedForStation($station),
'permission' => StationPermissions::WebHooks,
],
@ -204,32 +201,34 @@ return function (App\Event\BuildStationMenu $e) {
'label' => __('Mount Points'),
'icon' => 'wifi_tethering',
'url' => (string)$router->fromHere('stations:mounts:index'),
'visible' => $frontendEnum->supportsMounts(),
'visible' => StationFeatures::MountPoints->supportedForStation($station),
'permission' => StationPermissions::MountPoints,
],
'hls_streams' => [
'label' => __('HLS Streams'),
'url' => (string)$router->fromHere('stations:hls_streams:index'),
'visible' => $backendEnum->isEnabled() && $station->getEnableHls(),
'visible' => StationFeatures::HlsStreams->supportedForStation($station),
'permission' => StationPermissions::MountPoints,
],
'remotes' => [
'label' => __('Remote Relays'),
'icon' => 'router',
'url' => (string)$router->fromHere('stations:remotes:index'),
'visible' => StationFeatures::RemoteRelays->supportedForStation($station),
'permission' => StationPermissions::RemoteRelays,
],
'fallback' => [
'label' => __('Custom Fallback File'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:fallback'),
'visible' => StationFeatures::Media->supportedForStation($station),
'permission' => StationPermissions::Broadcasting,
],
'ls_config' => [
'label' => __('Edit Liquidsoap Configuration'),
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:util:ls_config'),
'visible' => $settings->getEnableAdvancedFeatures() && $backendEnum->isEnabled(),
'visible' => StationFeatures::CustomLiquidsoapConfig->supportedForStation($station),
'permission' => StationPermissions::Broadcasting,
],
'stations:stereo_tool_config' => [
@ -237,7 +236,7 @@ return function (App\Event\BuildStationMenu $e) {
'class' => 'text-muted',
'url' => (string)$router->fromHere('stations:stereo_tool_config'),
'visible' => $settings->getEnableAdvancedFeatures()
&& App\Radio\Enums\BackendAdapters::Liquidsoap === $backendEnum
&& StationFeatures::Media->supportedForStation($station)
&& AudioProcessingMethods::StereoTool === $backendConfig->getAudioProcessingMethodEnum(),
'permission' => StationPermissions::Broadcasting,
],

View File

@ -1,6 +1,7 @@
<?php
use App\Controller;
use App\Enums\StationFeatures;
use App\Enums\StationPermissions;
use App\Middleware;
use Slim\Routing\RouteCollectorProxy;
@ -87,8 +88,7 @@ return static function (RouteCollectorProxy $group) {
$group->get(
'/waveform/{media_id:[a-zA-Z0-9\-]+}.json',
Controller\Api\Stations\Waveform\GetWaveformAction::class
)
->setName('api:stations:media:waveform');
)->setName('api:stations:media:waveform');
$group->get('/art/{media_id:[a-zA-Z0-9\-]+}.jpg', Controller\Api\Stations\Art\GetArtAction::class)
->setName('api:stations:media:art');
@ -102,42 +102,9 @@ return static function (RouteCollectorProxy $group) {
$group->delete('/art/{media_id:[a-zA-Z0-9]+}', Controller\Api\Stations\Art\DeleteArtAction::class)
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->group(
'/liquidsoap-config',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\LiquidsoapConfig\GetAction::class
)->setName('api:stations:liquidsoap-config');
$group->put(
'',
Controller\Api\Stations\LiquidsoapConfig\PutAction::class
);
}
)->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
$group->group(
'/stereo_tool_config',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\StereoTool\GetStereoToolConfigurationAction::class
)->setName('api:stations:stereo_tool_config');
$group->post(
'',
Controller\Api\Stations\StereoTool\PostStereoToolConfigurationAction::class
);
$group->delete(
'',
Controller\Api\Stations\StereoTool\DeleteStereoToolConfigurationAction::class
);
}
)->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
// Public and private podcast pages
/*
* Podcast Public Pages
*/
$group->group(
'/podcast/{podcast_id}',
function (RouteCollectorProxy $group) {
@ -176,304 +143,424 @@ return static function (RouteCollectorProxy $group) {
}
)->add(Middleware\RequirePublishedPodcastEpisodeMiddleware::class);
// Private-only podcast pages
/*
* Podcast Private Pates
*/
$group->group(
'/podcasts',
'',
function (RouteCollectorProxy $group) {
$group->get('', Controller\Api\Stations\PodcastsController::class . ':listAction')
$group->get('/podcasts', Controller\Api\Stations\PodcastsController::class . ':listAction')
->setName('api:stations:podcasts');
$group->post('', Controller\Api\Stations\PodcastsController::class . ':createAction');
$group->post('/podcasts', Controller\Api\Stations\PodcastsController::class . ':createAction');
$group->post('/art', Controller\Api\Stations\Podcasts\Art\PostArtAction::class)
$group->post('/podcasts/art', Controller\Api\Stations\Podcasts\Art\PostArtAction::class)
->setName('api:stations:podcasts:new-art');
}
)->add(new Middleware\Permissions(StationPermissions::Podcasts, true));
$group->group(
'/podcast/{podcast_id}',
function (RouteCollectorProxy $group) {
$group->put('', Controller\Api\Stations\PodcastsController::class . ':editAction');
$group->delete('', Controller\Api\Stations\PodcastsController::class . ':deleteAction');
$group->post(
'/art',
Controller\Api\Stations\Podcasts\Art\PostArtAction::class
)->setName('api:stations:podcast:art-internal');
$group->delete(
'/art',
Controller\Api\Stations\Podcasts\Art\DeleteArtAction::class
);
$group->post(
'/episodes',
Controller\Api\Stations\PodcastEpisodesController::class . ':createAction'
);
$group->post(
'/episodes/art',
Controller\Api\Stations\Podcasts\Episodes\Art\PostArtAction::class
)->setName('api:stations:podcast:episodes:new-art');
$group->post(
'/episodes/media',
Controller\Api\Stations\Podcasts\Episodes\Media\PostMediaAction::class
)->setName('api:stations:podcast:episodes:new-media');
$group->group(
'/episode/{episode_id}',
'/podcast/{podcast_id}',
function (RouteCollectorProxy $group) {
$group->put(
'',
Controller\Api\Stations\PodcastEpisodesController::class . ':editAction'
);
$group->put('', Controller\Api\Stations\PodcastsController::class . ':editAction');
$group->delete(
'',
Controller\Api\Stations\PodcastEpisodesController::class . ':deleteAction'
);
$group->delete('', Controller\Api\Stations\PodcastsController::class . ':deleteAction');
$group->post(
'/art',
Controller\Api\Stations\Podcasts\Art\PostArtAction::class
)->setName('api:stations:podcast:art-internal');
$group->delete(
'/art',
Controller\Api\Stations\Podcasts\Art\DeleteArtAction::class
);
$group->post(
'/episodes',
Controller\Api\Stations\PodcastEpisodesController::class . ':createAction'
);
$group->post(
'/episodes/art',
Controller\Api\Stations\Podcasts\Episodes\Art\PostArtAction::class
)->setName('api:stations:podcast:episode:art-internal');
$group->delete(
'/art',
Controller\Api\Stations\Podcasts\Episodes\Art\DeleteArtAction::class
);
)->setName('api:stations:podcast:episodes:new-art');
$group->post(
'/media',
'/episodes/media',
Controller\Api\Stations\Podcasts\Episodes\Media\PostMediaAction::class
)->setName('api:stations:podcast:episode:media-internal');
)->setName('api:stations:podcast:episodes:new-media');
$group->delete(
'/media',
Controller\Api\Stations\Podcasts\Episodes\Media\DeleteMediaAction::class
$group->group(
'/episode/{episode_id}',
function (RouteCollectorProxy $group) {
$group->put(
'',
Controller\Api\Stations\PodcastEpisodesController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\PodcastEpisodesController::class . ':deleteAction'
);
$group->post(
'/art',
Controller\Api\Stations\Podcasts\Episodes\Art\PostArtAction::class
)->setName('api:stations:podcast:episode:art-internal');
$group->delete(
'/art',
Controller\Api\Stations\Podcasts\Episodes\Art\DeleteArtAction::class
);
$group->post(
'/media',
Controller\Api\Stations\Podcasts\Episodes\Media\PostMediaAction::class
)->setName('api:stations:podcast:episode:media-internal');
$group->delete(
'/media',
Controller\Api\Stations\Podcasts\Episodes\Media\DeleteMediaAction::class
);
}
);
}
);
}
)->add(new Middleware\Permissions(StationPermissions::Podcasts, true));
// Streamers public pages
$group->get(
'/streamer/{streamer_id}/art',
Controller\Api\Stations\Streamers\Art\GetArtAction::class
)->setName('api:stations:streamer:art');
// Streamers internal pages
$group->post('/streamers/art', Controller\Api\Stations\Streamers\Art\PostArtAction::class)
->setName('api:stations:streamers:new-art')
->add(new Middleware\Permissions(StationPermissions::Streamers, true));
/*
* Files/Media
*/
$group->group(
'/streamer/{streamer_id}',
'',
function (RouteCollectorProxy $group) {
$group->post(
'/art',
Controller\Api\Stations\Streamers\Art\PostArtAction::class
)->setName('api:stations:streamer:art-internal');
$group->group(
'/files',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\FilesController::class . ':listAction'
)->setName('api:stations:files');
$group->delete(
'/art',
Controller\Api\Stations\Streamers\Art\DeleteArtAction::class
$group->post(
'',
Controller\Api\Stations\FilesController::class . ':createAction'
);
$group->get('/list', Controller\Api\Stations\Files\ListAction::class)
->setName('api:stations:files:list');
$group->get('/directories', Controller\Api\Stations\Files\ListDirectoriesAction::class)
->setName('api:stations:files:directories');
$group->put('/rename', Controller\Api\Stations\Files\RenameAction::class)
->setName('api:stations:files:rename');
$group->put('/batch', Controller\Api\Stations\Files\BatchAction::class)
->setName('api:stations:files:batch');
$group->post('/mkdir', Controller\Api\Stations\Files\MakeDirectoryAction::class)
->setName('api:stations:files:mkdir');
$group->get('/bulk', Controller\Api\Stations\BulkMedia\DownloadAction::class)
->setName('api:stations:files:bulk');
$group->post('/bulk', Controller\Api\Stations\BulkMedia\UploadAction::class);
$group->get('/download', Controller\Api\Stations\Files\DownloadAction::class)
->setName('api:stations:files:download');
$group->map(
['GET', 'POST'],
'/upload',
Controller\Api\Stations\Files\FlowUploadAction::class
)->setName('api:stations:files:upload');
}
);
$group->group(
'/file/{id}',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\FilesController::class . ':getAction'
)->setName('api:stations:file');
$group->put(
'',
Controller\Api\Stations\FilesController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\FilesController::class . ':deleteAction'
);
$group->get('/play', Controller\Api\Stations\Files\PlayAction::class)
->setName('api:stations:files:play');
}
);
}
)->add(new Middleware\Permissions(StationPermissions::Streamers, true));
$station_api_endpoints = [
[
'file',
'files',
Controller\Api\Stations\FilesController::class,
StationPermissions::Media,
],
[
'hls_stream',
'hls_streams',
Controller\Api\Stations\HlsStreamsController::class,
StationPermissions::MountPoints,
],
[
'mount',
'mounts',
Controller\Api\Stations\MountsController::class,
StationPermissions::MountPoints,
],
[
'playlist',
'playlists',
Controller\Api\Stations\PlaylistsController::class,
StationPermissions::Media,
],
[
'remote',
'remotes',
Controller\Api\Stations\RemotesController::class,
StationPermissions::RemoteRelays,
],
[
'sftp-user',
'sftp-users',
Controller\Api\Stations\SftpUsersController::class,
StationPermissions::Media,
],
[
'streamer',
'streamers',
Controller\Api\Stations\StreamersController::class,
StationPermissions::Streamers,
],
[
'webhook',
'webhooks',
Controller\Api\Stations\WebhooksController::class,
StationPermissions::WebHooks,
],
];
foreach ($station_api_endpoints as [$singular, $plural, $class, $permission]) {
$group->group(
'',
function (RouteCollectorProxy $group) use ($singular, $plural, $class) {
$group->get('/' . $plural, $class . ':listAction')
->setName('api:stations:' . $plural);
$group->post('/' . $plural, $class . ':createAction');
$group->get('/' . $singular . '/{id}', $class . ':getAction')
->setName('api:stations:' . $singular);
$group->put('/' . $singular . '/{id}', $class . ':editAction');
$group->delete('/' . $singular . '/{id}', $class . ':deleteAction');
}
)->add(new Middleware\Permissions($permission, true));
}
$group->group(
'/files',
function (RouteCollectorProxy $group) {
$group->get('/list', Controller\Api\Stations\Files\ListAction::class)
->setName('api:stations:files:list');
$group->get('/directories', Controller\Api\Stations\Files\ListDirectoriesAction::class)
->setName('api:stations:files:directories');
$group->put('/rename', Controller\Api\Stations\Files\RenameAction::class)
->setName('api:stations:files:rename');
$group->put('/batch', Controller\Api\Stations\Files\BatchAction::class)
->setName('api:stations:files:batch');
$group->post('/mkdir', Controller\Api\Stations\Files\MakeDirectoryAction::class)
->setName('api:stations:files:mkdir');
$group->get('/play/{id}', Controller\Api\Stations\Files\PlayAction::class)
->setName('api:stations:files:play');
$group->get('/download', Controller\Api\Stations\Files\DownloadAction::class)
->setName('api:stations:files:download');
$group->get('/bulk', Controller\Api\Stations\BulkMedia\DownloadAction::class)
->setName('api:stations:files:bulk');
$group->post('/bulk', Controller\Api\Stations\BulkMedia\UploadAction::class);
$group->map(
['GET', 'POST'],
'/upload',
Controller\Api\Stations\Files\FlowUploadAction::class
)->setName('api:stations:files:upload');
}
)
->add(Middleware\Module\StationFiles::class)
)->add(new Middleware\StationSupportsFeature(StationFeatures::Media))
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->post(
'/mounts/intro',
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
)->setName('api:stations:mounts:new-intro')
/*
* SFTP Users
*/
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->get(
'/sftp-users',
Controller\Api\Stations\SftpUsersController::class . ':listAction'
)->setName('api:stations:sftp-users');
$group->post(
'/sftp-users',
Controller\Api\Stations\SftpUsersController::class . ':createAction'
);
$group->get(
'/sftp-user/{id}',
Controller\Api\Stations\SftpUsersController::class . ':getAction'
)->setName('api:stations:sftp-user');
$group->put(
'/sftp-user/{id}',
Controller\Api\Stations\SftpUsersController::class . ':editAction'
);
$group->delete(
'/sftp-user/{id}',
Controller\Api\Stations\SftpUsersController::class . ':deleteAction'
);
}
)->add(new Middleware\StationSupportsFeature(StationFeatures::Sftp))
->add(new Middleware\Permissions(StationPermissions::Media, true));
/*
* Mount Points
*/
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->group(
'/mounts',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\MountsController::class . ':listAction'
)->setName('api:stations:mounts');
$group->post(
'',
Controller\Api\Stations\MountsController::class . ':createAction'
);
$group->post(
'/intro',
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
)->setName('api:stations:mounts:new-intro');
}
);
$group->group(
'/mount/{id}',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\MountsController::class . ':getAction'
)->setName('api:stations:mount');
$group->put(
'',
Controller\Api\Stations\MountsController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\MountsController::class . ':deleteAction'
);
$group->get(
'/intro',
Controller\Api\Stations\Mounts\Intro\GetIntroAction::class
)->setName('api:stations:mounts:intro');
$group->post(
'/intro',
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
);
$group->delete(
'/intro',
Controller\Api\Stations\Mounts\Intro\DeleteIntroAction::class
);
}
);
}
)->add(new Middleware\StationSupportsFeature(StationFeatures::MountPoints))
->add(new Middleware\Permissions(StationPermissions::MountPoints, true));
/*
* Remote Relays
*/
$group->group(
'/mount/{id}',
'',
function (RouteCollectorProxy $group) {
$group->get(
'/intro',
Controller\Api\Stations\Mounts\Intro\GetIntroAction::class
)->setName('api:stations:mounts:intro');
'/remotes',
Controller\Api\Stations\RemotesController::class . ':listAction'
)->setName('api:stations:remotes');
$group->post(
'/intro',
Controller\Api\Stations\Mounts\Intro\PostIntroAction::class
'/remotes',
Controller\Api\Stations\RemotesController::class . ':createAction'
);
$group->get(
'/remote/{id}',
Controller\Api\Stations\RemotesController::class . ':getAction'
)->setName('api:stations:remote');
$group->put(
'/remote/{id}',
Controller\Api\Stations\RemotesController::class . ':editAction'
);
$group->delete(
'/intro',
Controller\Api\Stations\Mounts\Intro\DeleteIntroAction::class
'/remote/{id}',
Controller\Api\Stations\RemotesController::class . ':deleteAction'
);
}
)->add(new Middleware\Permissions(StationPermissions::MountPoints, true));
)->add(new Middleware\StationSupportsFeature(StationFeatures::RemoteRelays))
->add(new Middleware\Permissions(StationPermissions::RemoteRelays, true));
$group->get(
'/playlists/schedule',
Controller\Api\Stations\PlaylistsController::class . ':scheduleAction'
)
->setName('api:stations:playlists:schedule')
/*
* HLS Streams
*/
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->get(
'/hls_streams',
Controller\Api\Stations\HlsStreamsController::class . ':listAction'
)->setName('api:stations:hls_streams');
$group->post(
'/hls_streams',
Controller\Api\Stations\HlsStreamsController::class . ':createAction'
);
$group->get(
'/hls_stream/{id}',
Controller\Api\Stations\HlsStreamsController::class . ':getAction'
)->setName('api:stations:hls_stream');
$group->put(
'/hls_stream/{id}',
Controller\Api\Stations\HlsStreamsController::class . ':editAction'
);
$group->delete(
'/hls_stream/{id}',
Controller\Api\Stations\HlsStreamsController::class . ':deleteAction'
);
}
)->add(new Middleware\StationSupportsFeature(StationFeatures::HlsStreams))
->add(new Middleware\Permissions(StationPermissions::MountPoints, true));
/*
* Playlist
*/
$group->group(
'',
function (RouteCollectorProxy $group) {
$group->get(
'/playlists',
Controller\Api\Stations\PlaylistsController::class . ':listAction'
)->setName('api:stations:playlists');
$group->post(
'/playlists',
Controller\Api\Stations\PlaylistsController::class . ':createAction'
);
$group->get(
'/playlists/schedule',
Controller\Api\Stations\PlaylistsController::class . ':scheduleAction'
)->setName('api:stations:playlists:schedule');
$group->group(
'/playlist/{id}',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\PlaylistsController::class . ':getAction'
)->setName('api:stations:playlist');
$group->put(
'',
Controller\Api\Stations\PlaylistsController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\PlaylistsController::class . ':deleteAction'
);
$group->put(
'/toggle',
Controller\Api\Stations\Playlists\ToggleAction::class
)->setName('api:stations:playlist:toggle');
$group->put(
'/reshuffle',
Controller\Api\Stations\Playlists\ReshuffleAction::class
)->setName('api:stations:playlist:reshuffle');
$group->get(
'/order',
Controller\Api\Stations\Playlists\GetOrderAction::class
)->setName('api:stations:playlist:order');
$group->put(
'/order',
Controller\Api\Stations\Playlists\PutOrderAction::class
);
$group->get(
'/queue',
Controller\Api\Stations\Playlists\GetQueueAction::class
)->setName('api:stations:playlist:queue');
$group->delete(
'/queue',
Controller\Api\Stations\Playlists\DeleteQueueAction::class
);
$group->post(
'/clone',
Controller\Api\Stations\Playlists\CloneAction::class
)->setName('api:stations:playlist:clone');
$group->post(
'/import',
Controller\Api\Stations\Playlists\ImportAction::class
)->setName('api:stations:playlist:import');
$group->get(
'/export[/{format}]',
Controller\Api\Stations\Playlists\ExportAction::class
)->setName('api:stations:playlist:export');
}
);
}
)->add(new Middleware\StationSupportsFeature(StationFeatures::Media))
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->group(
'/playlist/{id}',
function (RouteCollectorProxy $group) {
$group->put(
'/toggle',
Controller\Api\Stations\Playlists\ToggleAction::class
)->setName('api:stations:playlist:toggle');
$group->put(
'/reshuffle',
Controller\Api\Stations\Playlists\ReshuffleAction::class
)->setName('api:stations:playlist:reshuffle');
$group->get(
'/order',
Controller\Api\Stations\Playlists\GetOrderAction::class
)->setName('api:stations:playlist:order');
$group->put(
'/order',
Controller\Api\Stations\Playlists\PutOrderAction::class
);
$group->get(
'/queue',
Controller\Api\Stations\Playlists\GetQueueAction::class
)->setName('api:stations:playlist:queue');
$group->delete(
'/queue',
Controller\Api\Stations\Playlists\DeleteQueueAction::class
);
$group->post(
'/clone',
Controller\Api\Stations\Playlists\CloneAction::class
)->setName('api:stations:playlist:clone');
$group->post(
'/import',
Controller\Api\Stations\Playlists\ImportAction::class
)->setName('api:stations:playlist:import');
$group->get(
'/export[/{format}]',
Controller\Api\Stations\Playlists\ExportAction::class
)->setName('api:stations:playlist:export');
}
)->add(new Middleware\Permissions(StationPermissions::Media, true));
/*
* Reports
*/
$group->group(
'/reports',
function (RouteCollectorProxy $group) {
@ -539,40 +626,94 @@ return static function (RouteCollectorProxy $group) {
}
)->add(new Middleware\Permissions(StationPermissions::Reports, true));
/*
* Streamers Extras
*/
$group->get(
'/streamers/schedule',
Controller\Api\Stations\StreamersController::class . ':scheduleAction'
)
->setName('api:stations:streamers:schedule')
->add(new Middleware\Permissions(StationPermissions::Streamers, true));
$group->get(
'/streamers/broadcasts',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':listAction'
)
->setName('api:stations:streamers:broadcasts')
->add(new Middleware\Permissions(StationPermissions::Streamers, true));
'/streamer/{id}/art',
Controller\Api\Stations\Streamers\Art\GetArtAction::class
)->setName('api:stations:streamer:art');
$group->group(
'/streamer/{streamer_id}',
'',
function (RouteCollectorProxy $group) {
$group->get(
'/broadcasts',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':listAction'
)->setName('api:stations:streamer:broadcasts');
$group->group(
'/streamers',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\StreamersController::class . ':listAction'
)->setName('api:stations:streamers');
$group->get(
'/broadcast/{broadcast_id}/download',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':downloadAction'
)->setName('api:stations:streamer:broadcast:download');
$group->post(
'',
Controller\Api\Stations\StreamersController::class . ':createAction'
);
$group->delete(
'/broadcast/{broadcast_id}',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':deleteAction'
)
->setName('api:stations:streamer:broadcast:delete');
$group->get(
'/schedule',
Controller\Api\Stations\StreamersController::class . ':scheduleAction'
)->setName('api:stations:streamers:schedule');
$group->get(
'/broadcasts',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':listAction'
)->setName('api:stations:streamers:broadcasts');
$group->post(
'/art',
Controller\Api\Stations\Streamers\Art\PostArtAction::class
)->setName('api:stations:streamers:new-art');
}
);
$group->group(
'/streamer/{id}',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\StreamersController::class . ':getAction'
)->setName('api:stations:streamer');
$group->put(
'',
Controller\Api\Stations\StreamersController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\StreamersController::class . ':deleteAction'
);
$group->get(
'/broadcasts',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':listAction'
)->setName('api:stations:streamer:broadcasts');
$group->get(
'/broadcast/{broadcast_id}/download',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':downloadAction'
)->setName('api:stations:streamer:broadcast:download');
$group->delete(
'/broadcast/{broadcast_id}',
Controller\Api\Stations\Streamers\BroadcastsController::class . ':deleteAction'
)->setName('api:stations:streamer:broadcast:delete');
$group->post(
'/art',
Controller\Api\Stations\Streamers\Art\PostArtAction::class
)->setName('api:stations:streamer:art-internal');
$group->delete(
'/art',
Controller\Api\Stations\Streamers\Art\DeleteArtAction::class
);
}
);
}
)->add(new Middleware\Permissions(StationPermissions::Streamers, true));
)->add(new Middleware\StationSupportsFeature(StationFeatures::Streamers))
->add(new Middleware\Permissions(StationPermissions::Streamers, true));
$group->get('/restart-status', Controller\Api\Stations\GetRestartStatusAction::class)
->setName('api:stations:restart-status')
@ -589,8 +730,7 @@ return static function (RouteCollectorProxy $group) {
$group->post(
'/frontend/{do}',
Controller\Api\Stations\ServicesController::class . ':frontendAction'
)
->setName('api:stations:frontend')
)->setName('api:stations:frontend')
->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
$group->post('/reload', Controller\Api\Stations\ServicesController::class . ':reloadAction')
@ -601,6 +741,9 @@ return static function (RouteCollectorProxy $group) {
->setName('api:stations:restart')
->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
/*
* Custom Fallback File
*/
$group->group(
'/fallback',
function (RouteCollectorProxy $group) {
@ -621,26 +764,104 @@ return static function (RouteCollectorProxy $group) {
}
)->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
/*
* Webhook Extras
*/
$group->group(
'/webhook/{id}',
'',
function (RouteCollectorProxy $group) {
$group->put(
'/toggle',
Controller\Api\Stations\Webhooks\ToggleAction::class
)->setName('api:stations:webhook:toggle');
$group->put(
'/test',
Controller\Api\Stations\Webhooks\TestAction::class
)->setName('api:stations:webhook:test');
$group->get(
'/test-log/{path}',
Controller\Api\Stations\Webhooks\TestLogAction::class
)->setName('api:stations:webhook:test-log');
}
)->add(new Middleware\Permissions(StationPermissions::WebHooks, true));
'/webhooks',
Controller\Api\Stations\WebhooksController::class . ':listAction'
)->setName('api:stations:webhooks');
$group->post(
'/webhooks',
Controller\Api\Stations\WebhooksController::class . ':createAction'
);
$group->group(
'/webhook/{id}',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\WebhooksController::class . ':getAction'
)->setName('api:stations:webhook');
$group->put(
'',
Controller\Api\Stations\WebhooksController::class . ':editAction'
);
$group->delete(
'',
Controller\Api\Stations\WebhooksController::class . ':deleteAction'
);
$group->put(
'/toggle',
Controller\Api\Stations\Webhooks\ToggleAction::class
)->setName('api:stations:webhook:toggle');
$group->put(
'/test',
Controller\Api\Stations\Webhooks\TestAction::class
)->setName('api:stations:webhook:test');
$group->get(
'/test-log/{path}',
Controller\Api\Stations\Webhooks\TestLogAction::class
)->setName('api:stations:webhook:test-log');
}
);
}
)->add(new Middleware\StationSupportsFeature(StationFeatures::Webhooks))
->add(new Middleware\Permissions(StationPermissions::WebHooks, true));
/*
* Custom Liquidsoap Configuration
*/
$group->group(
'/liquidsoap-config',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\LiquidsoapConfig\GetAction::class
)->setName('api:stations:liquidsoap-config');
$group->put(
'',
Controller\Api\Stations\LiquidsoapConfig\PutAction::class
);
}
)->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
/*
* StereoTool Configuration
*/
$group->group(
'/stereo_tool_config',
function (RouteCollectorProxy $group) {
$group->get(
'',
Controller\Api\Stations\StereoTool\GetStereoToolConfigurationAction::class
)->setName('api:stations:stereo_tool_config');
$group->post(
'',
Controller\Api\Stations\StereoTool\PostStereoToolConfigurationAction::class
);
$group->delete(
'',
Controller\Api\Stations\StereoTool\DeleteStereoToolConfigurationAction::class
);
}
)->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
/*
* Logs
*/
$group->group(
'',
function (RouteCollectorProxy $group) {

View File

@ -1,6 +1,7 @@
<?php
use App\Controller;
use App\Enums\StationFeatures;
use App\Enums\StationPermissions;
use App\Http\Response;
use App\Http\ServerRequest;
@ -30,14 +31,17 @@ return static function (RouteCollectorProxy $app) {
$group->get('/files', Controller\Stations\FilesAction::class)
->setName('stations:files:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Media))
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->get('/hls_streams', Controller\Stations\HlsStreamsAction::class)
->setName('stations:hls_streams:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::HlsStreams))
->add(new Middleware\Permissions(StationPermissions::MountPoints, true));
$group->get('/ls_config', Controller\Stations\EditLiquidsoapConfigAction::class)
->setName('stations:util:ls_config')
->add(new Middleware\StationSupportsFeature(StationFeatures::CustomLiquidsoapConfig))
->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
$group->get('/stereo_tool_config', Controller\Stations\UploadStereoToolConfigAction::class)
@ -50,14 +54,17 @@ return static function (RouteCollectorProxy $app) {
$group->get('/playlists', Controller\Stations\PlaylistsAction::class)
->setName('stations:playlists:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Media))
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->get('/podcasts', Controller\Stations\PodcastsAction::class)
->setName('stations:podcasts:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Podcasts))
->add(new Middleware\Permissions(StationPermissions::Podcasts, true));
$group->get('/mounts', Controller\Stations\MountsAction::class)
->setName('stations:mounts:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::MountPoints))
->add(new Middleware\Permissions(StationPermissions::MountPoints, true));
$group->get('/profile', Controller\Stations\ProfileController::class)
@ -76,10 +83,12 @@ return static function (RouteCollectorProxy $app) {
$group->get('/queue', Controller\Stations\QueueAction::class)
->setName('stations:queue:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Media))
->add(new Middleware\Permissions(StationPermissions::Broadcasting, true));
$group->get('/remotes', Controller\Stations\RemotesAction::class)
->setName('stations:remotes:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::RemoteRelays))
->add(new Middleware\Permissions(StationPermissions::RemoteRelays, true));
$group->group(
@ -108,14 +117,17 @@ return static function (RouteCollectorProxy $app) {
$group->get('/sftp_users', Controller\Stations\SftpUsersAction::class)
->setName('stations:sftp_users:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Sftp))
->add(new Middleware\Permissions(StationPermissions::Media, true));
$group->get('/streamers', Controller\Stations\StreamersAction::class)
->setName('stations:streamers:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Streamers))
->add(new Middleware\Permissions(StationPermissions::Streamers, true));
$group->get('/webhooks', Controller\Stations\WebhooksAction::class)
->setName('stations:webhooks:index')
->add(new Middleware\StationSupportsFeature(StationFeatures::Webhooks))
->add(new Middleware\Permissions(StationPermissions::WebHooks, true));
}
)

View File

@ -5,8 +5,6 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations;
use App\Entity;
use App\Exception\StationUnsupportedException;
use App\Http\ServerRequest;
use App\OpenApi;
use OpenApi\Attributes as OA;
@ -137,18 +135,4 @@ final class HlsStreamsController extends AbstractStationApiCrudController
{
protected string $entityClass = Entity\StationHlsStream::class;
protected string $resourceRouteName = 'api:stations:hls_stream';
/**
* @inheritDoc
*/
protected function getStation(ServerRequest $request): Entity\Station
{
$station = parent::getStation($request);
if (!$station->getBackendTypeEnum()->isEnabled()) {
throw new StationUnsupportedException();
}
return $station;
}
}

View File

@ -7,7 +7,6 @@ namespace App\Controller\Api\Stations;
use App\Controller\Api\Traits\CanSortResults;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Exception\StationUnsupportedException;
use App\Http\Response;
use App\Http\Router;
use App\Http\ServerRequest;
@ -256,18 +255,4 @@ final class MountsController extends AbstractStationApiCrudController
return $response->withJson(Entity\Api\Status::deleted());
}
/**
* @inheritDoc
*/
protected function getStation(ServerRequest $request): Entity\Station
{
$station = parent::getStation($request);
if (!$station->getFrontendTypeEnum()->supportsMounts()) {
throw new StationUnsupportedException();
}
return $station;
}
}

View File

@ -21,11 +21,11 @@ final class DeleteArtAction
ServerRequest $request,
Response $response,
string $station_id,
string $streamer_id
string $id
): ResponseInterface {
$station = $request->getStation();
$streamer = $this->streamerRepo->requireForStation($streamer_id, $station);
$streamer = $this->streamerRepo->requireForStation($id, $station);
$this->streamerRepo->removeArtwork($streamer);
$this->streamerRepo->getEntityManager()

View File

@ -21,14 +21,14 @@ final class GetArtAction
ServerRequest $request,
Response $response,
string $station_id,
string $streamer_id
string $id
): ResponseInterface {
// If a timestamp delimiter is added, strip it automatically.
$streamer_id = explode('|', $streamer_id, 2)[0];
$id = explode('|', $id, 2)[0];
$station = $request->getStation();
$artworkPath = Entity\StationStreamer::getArtworkPath($streamer_id);
$artworkPath = Entity\StationStreamer::getArtworkPath($id);
$fsConfig = (new StationFilesystems($station))->getConfigFilesystem();
if ($fsConfig->fileExists($artworkPath)) {

View File

@ -21,7 +21,7 @@ final class PostArtAction
ServerRequest $request,
Response $response,
string $station_id,
?string $streamer_id = null
?string $id = null
): ResponseInterface {
$station = $request->getStation();
@ -30,8 +30,8 @@ final class PostArtAction
return $flowResponse;
}
if (null !== $streamer_id) {
$streamer = $this->streamerRepo->requireForStation($streamer_id, $station);
if (null !== $id) {
$streamer = $this->streamerRepo->requireForStation($id, $station);
$this->streamerRepo->writeArtwork(
$streamer,

View File

@ -25,12 +25,12 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request,
Response $response,
string $station_id,
?string $streamer_id = null
?string $id = null
): ResponseInterface {
$station = $request->getStation();
if (null !== $streamer_id) {
$streamer = $this->getStreamer($station, $streamer_id);
if (null !== $id) {
$streamer = $this->getStreamer($station, $id);
if (null === $streamer) {
return $response->withStatus(404)
@ -66,13 +66,13 @@ final class BroadcastsController extends AbstractApiCrudController
$fsRecordings = (new StationFilesystems($station))->getRecordingsFilesystem();
$paginator->setPostprocessor(
function ($row) use ($streamer_id, $is_bootgrid, $router, $fsRecordings) {
function ($row) use ($id, $is_bootgrid, $router, $fsRecordings) {
$return = $this->toArray($row);
unset($return['recordingPath']);
$recordingPath = $row->getRecordingPath();
if (null === $streamer_id) {
if (null === $id) {
$streamer = $row->getStreamer();
$return['streamer'] = [
'id' => $streamer->getId(),
@ -85,7 +85,7 @@ final class BroadcastsController extends AbstractApiCrudController
$routeParams = [
'broadcast_id' => $row->getId(),
];
if (null === $streamer_id) {
if (null === $id) {
$routeParams['id'] = $row->getStreamer()->getId();
}
@ -126,7 +126,7 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request,
Response $response,
string $station_id,
string $streamer_id,
string $id,
string $broadcast_id
): ResponseInterface {
$station = $request->getStation();
@ -159,7 +159,7 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request,
Response $response,
string $station_id,
string $streamer_id,
string $id,
string $broadcast_id
): ResponseInterface {
$station = $request->getStation();

View File

@ -7,7 +7,6 @@ namespace App\Controller\Api\Stations;
use App\Controller\Api\Traits\CanSortResults;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Exception\StationUnsupportedException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\OpenApi;
@ -286,38 +285,24 @@ final class StreamersController extends AbstractScheduledEntityController
$return['has_custom_art'] = (0 !== $record->getArtUpdatedAt());
$return['art'] = (string)$router->fromHere(
route_name: 'api:stations:streamer:art',
route_params: ['streamer_id' => $record->getIdRequired() . '|' . $record->getArtUpdatedAt()],
route_params: ['id' => $record->getIdRequired() . '|' . $record->getArtUpdatedAt()],
absolute: !$isInternal
);
$return['links']['broadcasts'] = (string)$router->fromHere(
route_name: 'api:stations:streamer:broadcasts',
route_params: ['streamer_id' => $record->getId()],
route_params: ['id' => $record->getId()],
absolute: !$isInternal
);
$return['links']['art'] = (string)$router->fromHere(
route_name: 'api:stations:streamer:art-internal',
route_params: ['streamer_id' => $record->getId()],
route_params: ['id' => $record->getId()],
absolute: !$isInternal
);
return $return;
}
/**
* @inheritDoc
*/
protected function getStation(ServerRequest $request): Entity\Station
{
$station = parent::getStation($request);
if (!$station->getBackendTypeEnum()->isEnabled()) {
throw new StationUnsupportedException();
}
return $station;
}
protected function deleteRecord(object $record): void
{
if (!($record instanceof Entity\StationStreamer)) {

View File

@ -6,7 +6,6 @@ namespace App\Controller\Stations;
use App\Entity\StationBackendConfiguration;
use App\Event\Radio\WriteLiquidsoapConfiguration;
use App\Exception\StationUnsupportedException;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Radio\Backend\Liquidsoap;
@ -27,10 +26,6 @@ final class EditLiquidsoapConfigAction
): ResponseInterface {
$station = $request->getStation();
if (!$station->getBackendTypeEnum()->isEnabled()) {
throw new StationUnsupportedException();
}
$configSections = StationBackendConfiguration::getCustomConfigurationSections();
$tokens = Liquidsoap\ConfigWriter::getDividerString();

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Controller\Stations;
use App\Exception\StationUnsupportedException;
use App\Http\Response;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
@ -16,12 +15,6 @@ final class HlsStreamsAction
Response $response,
string $station_id
): ResponseInterface {
$station = $request->getStation();
if (!$station->getBackendTypeEnum()->isEnabled() || !$station->getEnableHls()) {
throw new StationUnsupportedException();
}
$router = $request->getRouter();
return $request->getView()->renderVuePage(
response: $response,

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Enums;
use App\Entity\Station;
enum StationFeatures
{
case CustomLiquidsoapConfig;
case Media;
case Sftp;
case MountPoints;
case RemoteRelays;
case HlsStreams;
case Streamers;
case Webhooks;
case Podcasts;
public function supportedForStation(Station $station): bool
{
$backendEnabled = $station->getBackendTypeEnum()->isEnabled();
return match ($this) {
self::Media, self::RemoteRelays, self::CustomLiquidsoapConfig => $backendEnabled,
self::Streamers => $backendEnabled && $station->getEnableStreamers(),
self::Sftp => $backendEnabled && $station->getMediaStorageLocation()->isLocal(),
self::MountPoints => $station->getFrontendTypeEnum()->supportsMounts(),
self::HlsStreams => $backendEnabled && $station->getEnableHls(),
self::Webhooks, self::Podcasts => true,
};
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace App\Middleware;
use App\Enums\StationFeatures;
use App\Exception;
use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class StationSupportsFeature
{
public function __construct(
private readonly StationFeatures $feature
) {
}
public function __invoke(ServerRequest $request, RequestHandlerInterface $handler): ResponseInterface
{
if (!$this->feature->supportedForStation($request->getStation())) {
throw new Exception\StationUnsupportedException();
}
return $handler->handle($request);
}
}

View File

@ -14,6 +14,10 @@ class Api_Stations_StreamersCest extends CestAbstract
// Create new record
$station = $this->getTestStation();
$station->setEnableStreamers(true);
$this->em->persist($station);
$this->em->flush();
$listUrl = '/api/station/' . $station->getId() . '/streamers';
$I->sendPOST(