Bug fixes, Repo and CustomAsset cleanup.

This commit is contained in:
Buster "Silver Eagle" Neece 2022-05-31 02:50:49 -05:00
parent 3c1bec857b
commit b6767e1bc3
No known key found for this signature in database
GPG Key ID: F1D2E64A0005E80E
63 changed files with 337 additions and 490 deletions

View File

@ -10,11 +10,6 @@ use Psr\Http\Message\UriInterface;
abstract class AbstractCustomAsset implements CustomAssetInterface abstract class AbstractCustomAsset implements CustomAssetInterface
{ {
public function __construct(
protected Environment $environment
) {
}
abstract protected function getPattern(): string; abstract protected function getPattern(): string;
abstract protected function getDefaultUrl(): string; abstract protected function getDefaultUrl(): string;
@ -22,7 +17,7 @@ abstract class AbstractCustomAsset implements CustomAssetInterface
public function getPath(): string public function getPath(): string
{ {
$pattern = sprintf($this->getPattern(), ''); $pattern = sprintf($this->getPattern(), '');
return $this->environment->getUploadsDirectory() . '/' . $pattern; return Environment::getInstance()->getUploadsDirectory() . '/' . $pattern;
} }
public function getUrl(): string public function getUrl(): string
@ -32,7 +27,7 @@ abstract class AbstractCustomAsset implements CustomAssetInterface
$pattern = $this->getPattern(); $pattern = $this->getPattern();
$mtime = filemtime($path); $mtime = filemtime($path);
return $this->environment->getAssetUrl() . self::UPLOADS_URL_PREFIX . '/' . sprintf( return Environment::getInstance()->getAssetUrl() . self::UPLOADS_URL_PREFIX . '/' . sprintf(
$pattern, $pattern,
'.' . $mtime '.' . $mtime
); );

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Assets; namespace App\Assets;
use App\Environment;
use Intervention\Image\Constraint; use Intervention\Image\Constraint;
use Intervention\Image\Image; use Intervention\Image\Image;
@ -16,7 +17,7 @@ class AlbumArtCustomAsset extends AbstractCustomAsset
protected function getDefaultUrl(): string protected function getDefaultUrl(): string
{ {
return $this->environment->getAssetUrl() . '/img/generic_song.jpg'; return Environment::getInstance()->getAssetUrl() . '/img/generic_song.jpg';
} }
public function upload(Image $image): void public function upload(Image $image): void

View File

@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Assets;
use App\Environment;
use InvalidArgumentException;
class AssetFactory
{
public const TYPE_ALBUM_ART = 'album_art';
public const TYPE_BACKGROUND = 'background';
public const TYPE_BROWSER_ICON = 'browser_icon';
public static function createAlbumArt(Environment $environment): AlbumArtCustomAsset
{
return new AlbumArtCustomAsset($environment);
}
public static function createBackground(Environment $environment): BackgroundCustomAsset
{
return new BackgroundCustomAsset($environment);
}
public static function createBrowserIcon(Environment $environment): BrowserIconCustomAsset
{
return new BrowserIconCustomAsset($environment);
}
public static function createForType(Environment $environment, string $type): CustomAssetInterface
{
return match ($type) {
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.')
};
}
}

23
src/Assets/AssetTypes.php Normal file
View File

@ -0,0 +1,23 @@
<?php
// phpcs:ignoreFile
declare(strict_types=1);
namespace App\Assets;
enum AssetTypes: string
{
case AlbumArt = 'album_art';
case Background = 'background';
case BrowserIcon = 'browser_icon';
public function createObject(): CustomAssetInterface
{
return match ($this) {
self::AlbumArt => new AlbumArtCustomAsset(),
self::Background => new BackgroundCustomAsset(),
self::BrowserIcon => new BrowserIconCustomAsset(),
};
}
}

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Assets; namespace App\Assets;
use App\Environment;
use Intervention\Image\Constraint; use Intervention\Image\Constraint;
use Intervention\Image\Image; use Intervention\Image\Image;
@ -16,7 +17,7 @@ class BackgroundCustomAsset extends AbstractCustomAsset
protected function getDefaultUrl(): string protected function getDefaultUrl(): string
{ {
return $this->environment->getAssetUrl() . '/img/hexbg.png'; return Environment::getInstance()->getAssetUrl() . '/img/hexbg.png';
} }
public function upload(Image $image): void public function upload(Image $image): void

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Assets; namespace App\Assets;
use App\Environment;
use Intervention\Image\Image; use Intervention\Image\Image;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
@ -34,13 +35,15 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
protected function getDefaultUrl(): string protected function getDefaultUrl(): string
{ {
$assetUrl = $this->environment->getAssetUrl(); $env = Environment::getInstance();
return $assetUrl . '/icons/' . $this->environment->getAppEnvironmentEnum()->value . '/original.png';
$assetUrl = $env->getAssetUrl();
return $assetUrl . '/icons/' . $env->getAppEnvironmentEnum()->value . '/original.png';
} }
public function upload(Image $image): void public function upload(Image $image): void
{ {
$uploadsDir = $this->environment->getUploadsDirectory() . '/browser_icon'; $uploadsDir = Environment::getInstance()->getUploadsDirectory() . '/browser_icon';
(new Filesystem())->mkdir($uploadsDir); (new Filesystem())->mkdir($uploadsDir);
$newImage = clone $image; $newImage = clone $image;
@ -56,15 +59,16 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
public function delete(): void public function delete(): void
{ {
$uploadsDir = $this->environment->getUploadsDirectory() . '/browser_icon'; $uploadsDir = Environment::getInstance()->getUploadsDirectory() . '/browser_icon';
(new Filesystem())->remove($uploadsDir); (new Filesystem())->remove($uploadsDir);
} }
public function getUrlForSize(int $size): string public function getUrlForSize(int $size): string
{ {
$assetUrl = $this->environment->getAssetUrl(); $env = Environment::getInstance();
$assetUrl = $env->getAssetUrl();
$uploadsDir = $this->environment->getUploadsDirectory(); $uploadsDir = $env->getUploadsDirectory();
$iconPath = $uploadsDir . '/browser_icon/' . $size . '.png'; $iconPath = $uploadsDir . '/browser_icon/' . $size . '.png';
if (is_file($iconPath)) { if (is_file($iconPath)) {
@ -72,6 +76,6 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
return $assetUrl . '/uploads/browser_icon/' . $size . '.' . $mtime . '.png'; return $assetUrl . '/uploads/browser_icon/' . $size . '.' . $mtime . '.png';
} }
return $assetUrl . '/icons/' . $this->environment->getAppEnvironmentEnum()->value . '/' . $size . '.png'; return $assetUrl . '/icons/' . $env->getAppEnvironmentEnum()->value . '/' . $size . '.png';
} }
} }

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace App\Controller\Admin; namespace App\Controller\Admin;
use App\Assets\AssetFactory; use App\Assets\AssetTypes;
use App\Entity\Settings; use App\Entity\Settings;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
@ -28,13 +28,13 @@ final class BrandingAction
'group' => Settings::GROUP_BRANDING, 'group' => Settings::GROUP_BRANDING,
]), ]),
'browserIconApiUrl' => (string)$router->named('api:admin:custom_assets', [ 'browserIconApiUrl' => (string)$router->named('api:admin:custom_assets', [
'type' => AssetFactory::TYPE_BROWSER_ICON, 'type' => AssetTypes::BrowserIcon->value,
]), ]),
'backgroundApiUrl' => (string)$router->named('api:admin:custom_assets', [ 'backgroundApiUrl' => (string)$router->named('api:admin:custom_assets', [
'type' => AssetFactory::TYPE_BACKGROUND, 'type' => AssetTypes::Background->value,
]), ]),
'albumArtApiUrl' => (string)$router->named('api:admin:custom_assets', [ 'albumArtApiUrl' => (string)$router->named('api:admin:custom_assets', [
'type' => AssetFactory::TYPE_ALBUM_ART, 'type' => AssetTypes::AlbumArt->value,
]), ]),
], ],
); );

View File

@ -89,7 +89,7 @@ final class LogsController extends AbstractLogViewerController
public function viewAction( public function viewAction(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string|int $station_id, string $station_id,
string $log string $log
): ResponseInterface { ): ResponseInterface {
if ('global' === $station_id) { if ('global' === $station_id) {

View File

@ -4,27 +4,20 @@ declare(strict_types=1);
namespace App\Controller\Api\Admin\CustomAssets; namespace App\Controller\Api\Admin\CustomAssets;
use App\Assets\AssetFactory; use App\Assets\AssetTypes;
use App\Entity; use App\Entity;
use App\Environment;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class DeleteCustomAssetAction final class DeleteCustomAssetAction
{ {
public function __construct(
private readonly Environment $environment,
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $type string $type
): ResponseInterface { ): ResponseInterface {
$customAsset = AssetFactory::createForType($this->environment, $type); $customAsset = AssetTypes::from($type)->createObject();
$customAsset->delete(); $customAsset->delete();
return $response->withJson(Entity\Api\Status::success()); return $response->withJson(Entity\Api\Status::success());

View File

@ -4,25 +4,19 @@ declare(strict_types=1);
namespace App\Controller\Api\Admin\CustomAssets; namespace App\Controller\Api\Admin\CustomAssets;
use App\Assets\AssetFactory; use App\Assets\AssetTypes;
use App\Environment;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class GetCustomAssetAction final class GetCustomAssetAction
{ {
public function __construct(
private readonly Environment $environment,
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $type string $type
): ResponseInterface { ): ResponseInterface {
$customAsset = AssetFactory::createForType($this->environment, $type); $customAsset = AssetTypes::from($type)->createObject();
return $response->withJson( return $response->withJson(
[ [

View File

@ -4,9 +4,8 @@ declare(strict_types=1);
namespace App\Controller\Api\Admin\CustomAssets; namespace App\Controller\Api\Admin\CustomAssets;
use App\Assets\AssetFactory; use App\Assets\AssetTypes;
use App\Entity; use App\Entity;
use App\Environment;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Media\AlbumArt; use App\Media\AlbumArt;
@ -15,17 +14,12 @@ use Psr\Http\Message\ResponseInterface;
final class PostCustomAssetAction final class PostCustomAssetAction
{ {
public function __construct(
private readonly Environment $environment
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $type string $type
): ResponseInterface { ): ResponseInterface {
$customAsset = AssetFactory::createForType($this->environment, $type); $customAsset = AssetTypes::from($type)->createObject();
$flowResponse = Flow::process($request, $response); $flowResponse = Flow::process($request, $response);
if ($flowResponse instanceof ResponseInterface) { if ($flowResponse instanceof ResponseInterface) {

View File

@ -53,12 +53,7 @@ final class DeleteArtAction
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$media = $this->mediaRepo->findForStation($media_id, $station); $media = $this->mediaRepo->requireForStation($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$this->mediaRepo->removeAlbumArt($media); $this->mediaRepo->removeAlbumArt($media);
return $response->withJson(Entity\Api\Status::deleted()); return $response->withJson(Entity\Api\Status::deleted());

View File

@ -56,11 +56,7 @@ final class PostArtAction
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$media = $this->mediaRepo->findForStation($media_id, $station); $media = $this->mediaRepo->requireForStation($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$flowResponse = Flow::process($request, $response, $station->getRadioTempDir()); $flowResponse = Flow::process($request, $response, $station->getRadioTempDir());
if ($flowResponse instanceof ResponseInterface) { if ($flowResponse instanceof ResponseInterface) {

View File

@ -27,12 +27,7 @@ final class PlayAction
$station = $request->getStation(); $station = $request->getStation();
$media = $this->mediaRepo->findForStation($id, $station); $media = $this->mediaRepo->requireForStation($id, $station);
if (!$media instanceof Entity\StationMedia) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$fsMedia = (new StationFilesystems($station))->getMediaFilesystem(); $fsMedia = (new StationFilesystems($station))->getMediaFilesystem();

View File

@ -47,13 +47,8 @@ final class DeleteIntroAction
string $id string $id
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$mount = $this->mountRepo->findForStation($id, $station);
if (null === $mount) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$mount = $this->mountRepo->requireForStation($id, $station);
$this->mountRepo->clearIntro($mount); $this->mountRepo->clearIntro($mount);
return $response->withJson(Entity\Api\Status::deleted()); return $response->withJson(Entity\Api\Status::deleted());

View File

@ -55,12 +55,7 @@ final class PostIntroAction
} }
if (null !== $id) { if (null !== $id) {
$mount = $this->mountRepo->findForStation($id, $station); $mount = $this->mountRepo->requireForStation($id, $station);
if (null === $mount) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$this->mountRepo->setIntro($mount, $flowResponse); $this->mountRepo->setIntro($mount, $flowResponse);
return $response->withJson(Entity\Api\Status::updated()); return $response->withJson(Entity\Api\Status::updated());

View File

@ -31,12 +31,7 @@ final class DownloadAction
->withJson(new Entity\Api\Error(403, __('This station does not support on-demand streaming.'))); ->withJson(new Entity\Api\Error(403, __('This station does not support on-demand streaming.')));
} }
$media = $this->mediaRepo->findByUniqueId($media_id, $station); $media = $this->mediaRepo->requireByUniqueId($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$fsMedia = (new StationFilesystems($station))->getMediaFilesystem(); $fsMedia = (new StationFilesystems($station))->getMediaFilesystem();

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists;
use App\Entity;
use App\Exception\NotFoundException;
use Doctrine\ORM\EntityManagerInterface;
abstract class AbstractPlaylistsAction
{
public function __construct(
protected EntityManagerInterface $em
) {
}
protected function requireRecord(Entity\Station $station, int|string $id): Entity\StationPlaylist
{
$record = $this->em->getRepository(Entity\StationPlaylist::class)->findOneBy(
[
'station' => $station,
'id' => (int)$id,
]
);
if (!$record instanceof Entity\StationPlaylist) {
throw new NotFoundException(__('Playlist not found.'));
}
return $record;
}
}

View File

@ -4,22 +4,34 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity\Api\Status;
use App\Entity\Repository\StationPlaylistRepository;
use App\Entity\StationPlaylist;
use App\Entity\StationPlaylistFolder;
use App\Entity\StationPlaylistMedia;
use App\Entity\StationSchedule;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use DeepCopy; use DeepCopy;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class CloneAction extends AbstractPlaylistsAction final class CloneAction
{ {
public function __construct(
private readonly StationPlaylistRepository $playlistRepo,
private readonly ReloadableEntityManagerInterface $em,
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
$data = (array)$request->getParsedBody(); $data = (array)$request->getParsedBody();
@ -43,10 +55,10 @@ final class CloneAction extends AbstractPlaylistsAction
); );
$copier->addFilter( $copier->addFilter(
new DeepCopy\Filter\KeepFilter(), new DeepCopy\Filter\KeepFilter(),
new DeepCopy\Matcher\PropertyMatcher(Entity\StationPlaylistMedia::class, 'media') new DeepCopy\Matcher\PropertyMatcher(StationPlaylistMedia::class, 'media')
); );
/** @var Entity\StationPlaylist $newRecord */ /** @var StationPlaylist $newRecord */
$newRecord = $copier->copy($record); $newRecord = $copier->copy($record);
$newRecord->setName($data['name'] ?? ($record->getName() . ' - Copy')); $newRecord->setName($data['name'] ?? ($record->getName() . ' - Copy'));
@ -57,7 +69,7 @@ final class CloneAction extends AbstractPlaylistsAction
if (in_array('schedule', $toClone, true)) { if (in_array('schedule', $toClone, true)) {
foreach ($record->getScheduleItems() as $oldScheduleItem) { foreach ($record->getScheduleItems() as $oldScheduleItem) {
/** @var Entity\StationSchedule $newScheduleItem */ /** @var StationSchedule $newScheduleItem */
$newScheduleItem = $copier->copy($oldScheduleItem); $newScheduleItem = $copier->copy($oldScheduleItem);
$newScheduleItem->setPlaylist($newRecord); $newScheduleItem->setPlaylist($newRecord);
@ -67,14 +79,14 @@ final class CloneAction extends AbstractPlaylistsAction
if (in_array('media', $toClone, true)) { if (in_array('media', $toClone, true)) {
foreach ($record->getFolders() as $oldPlaylistFolder) { foreach ($record->getFolders() as $oldPlaylistFolder) {
/** @var Entity\StationPlaylistFolder $newPlaylistFolder */ /** @var StationPlaylistFolder $newPlaylistFolder */
$newPlaylistFolder = $copier->copy($oldPlaylistFolder); $newPlaylistFolder = $copier->copy($oldPlaylistFolder);
$newPlaylistFolder->setPlaylist($newRecord); $newPlaylistFolder->setPlaylist($newRecord);
$this->em->persist($newPlaylistFolder); $this->em->persist($newPlaylistFolder);
} }
foreach ($record->getMediaItems() as $oldMediaItem) { foreach ($record->getMediaItems() as $oldMediaItem) {
/** @var Entity\StationPlaylistMedia $newMediaItem */ /** @var StationPlaylistMedia $newMediaItem */
$newMediaItem = $copier->copy($oldMediaItem); $newMediaItem = $copier->copy($oldMediaItem);
$newMediaItem->setPlaylist($newRecord); $newMediaItem->setPlaylist($newRecord);
@ -84,6 +96,6 @@ final class CloneAction extends AbstractPlaylistsAction
$this->em->flush(); $this->em->flush();
return $response->withJson(Entity\Api\Status::created()); return $response->withJson(Status::created());
} }
} }

View File

@ -4,19 +4,19 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Entity\Api\Status;
use App\Entity\Repository\StationPlaylistMediaRepository;
use App\Entity\Repository\StationPlaylistRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class DeleteQueueAction extends AbstractPlaylistsAction final class DeleteQueueAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationPlaylistRepository $playlistRepo,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo, private readonly StationPlaylistMediaRepository $spmRepo,
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -25,12 +25,12 @@ final class DeleteQueueAction extends AbstractPlaylistsAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
$this->spmRepo->resetQueue($record); $this->spmRepo->resetQueue($record);
return $response->withJson( return $response->withJson(
new Entity\Api\Status( new Status(
true, true,
__('Playlist queue cleared.') __('Playlist queue cleared.')
) )

View File

@ -4,13 +4,19 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity\Repository\StationPlaylistRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class ExportAction extends AbstractPlaylistsAction final class ExportAction
{ {
public function __construct(
private readonly StationPlaylistRepository $playlistRepo
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
@ -18,7 +24,7 @@ final class ExportAction extends AbstractPlaylistsAction
string $id, string $id,
string $format = 'pls' string $format = 'pls'
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
$exportFileName = 'playlist_' . $record->getShortName() . '.' . $format; $exportFileName = 'playlist_' . $record->getShortName() . '.' . $format;
$exportLines = []; $exportLines = [];

View File

@ -4,14 +4,23 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity\Enums\PlaylistOrders;
use App\Entity\Enums\PlaylistSources;
use App\Entity\Repository\StationPlaylistRepository;
use App\Exception; use App\Exception;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class GetOrderAction extends AbstractPlaylistsAction final class GetOrderAction
{ {
public function __construct(
private readonly StationPlaylistRepository $playlistRepo,
private readonly ReloadableEntityManagerInterface $em,
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
@ -19,11 +28,11 @@ final class GetOrderAction extends AbstractPlaylistsAction
string $id string $id
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$record = $this->requireRecord($station, $id); $record = $this->playlistRepo->requireForStation($id, $station);
if ( if (
Entity\Enums\PlaylistSources::Songs !== $record->getSourceEnum() PlaylistSources::Songs !== $record->getSourceEnum()
|| Entity\Enums\PlaylistOrders::Sequential !== $record->getOrderEnum() || PlaylistOrders::Sequential !== $record->getOrderEnum()
) { ) {
throw new Exception(__('This playlist is not a sequential playlist.')); throw new Exception(__('This playlist is not a sequential playlist.'));
} }

View File

@ -4,21 +4,22 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Entity\Enums\PlaylistOrders;
use App\Entity\Enums\PlaylistSources;
use App\Entity\Repository\StationPlaylistMediaRepository;
use App\Entity\Repository\StationPlaylistRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Paginator; use App\Paginator;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class GetQueueAction extends AbstractPlaylistsAction final class GetQueueAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationPlaylistRepository $playlistRepo,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo private readonly StationPlaylistMediaRepository $spmRepo
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -27,13 +28,13 @@ final class GetQueueAction extends AbstractPlaylistsAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
if (Entity\Enums\PlaylistSources::Songs !== $record->getSourceEnum()) { if (PlaylistSources::Songs !== $record->getSourceEnum()) {
throw new InvalidArgumentException('This playlist does not have songs as its primary source.'); throw new InvalidArgumentException('This playlist does not have songs as its primary source.');
} }
if (Entity\Enums\PlaylistOrders::Random === $record->getOrderEnum()) { if (PlaylistOrders::Random === $record->getOrderEnum()) {
throw new InvalidArgumentException('This playlist is always shuffled and has no visible queue.'); throw new InvalidArgumentException('This playlist is always shuffled and has no visible queue.');
} }

View File

@ -4,22 +4,26 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity\Api\Error;
use App\Entity\Api\StationPlaylistImportResult;
use App\Entity\Repository\StationPlaylistMediaRepository;
use App\Entity\Repository\StationPlaylistRepository;
use App\Entity\StationMedia;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Radio\PlaylistParser; use App\Radio\PlaylistParser;
use App\Utilities\File; use App\Utilities\File;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface; use Psr\Http\Message\UploadedFileInterface;
final class ImportAction extends AbstractPlaylistsAction final class ImportAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationPlaylistRepository $playlistRepo,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo, private readonly StationPlaylistMediaRepository $spmRepo,
private readonly ReloadableEntityManagerInterface $em,
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -28,13 +32,13 @@ final class ImportAction extends AbstractPlaylistsAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$playlist = $this->requireRecord($request->getStation(), $id); $playlist = $this->playlistRepo->requireForStation($id, $request->getStation());
$files = $request->getUploadedFiles(); $files = $request->getUploadedFiles();
if (empty($files['playlist_file'])) { if (empty($files['playlist_file'])) {
return $response->withStatus(500) return $response->withStatus(500)
->withJson(new Entity\Api\Error(500, 'No "playlist_file" provided.')); ->withJson(new Error(500, 'No "playlist_file" provided.'));
} }
/** @var UploadedFileInterface $file */ /** @var UploadedFileInterface $file */
@ -42,7 +46,7 @@ final class ImportAction extends AbstractPlaylistsAction
if (UPLOAD_ERR_OK !== $file->getError()) { if (UPLOAD_ERR_OK !== $file->getError()) {
return $response->withStatus(500) return $response->withStatus(500)
->withJson(Entity\Api\Error::fromFileError($file->getError())); ->withJson(Error::fromFileError($file->getError()));
} }
$playlistFile = $file->getStream()->getContents(); $playlistFile = $file->getStream()->getContents();
@ -137,10 +141,10 @@ final class ImportAction extends AbstractPlaylistsAction
->setParameter('matched_ids', $matches) ->setParameter('matched_ids', $matches)
->execute(); ->execute();
/** @var Entity\StationMedia[] $mediaById */ /** @var StationMedia[] $mediaById */
$mediaById = []; $mediaById = [];
foreach ($matchedMediaRaw as $row) { foreach ($matchedMediaRaw as $row) {
/** @var Entity\StationMedia $row */ /** @var StationMedia $row */
$mediaById[$row->getId()] = $row; $mediaById[$row->getId()] = $row;
} }
@ -161,7 +165,7 @@ final class ImportAction extends AbstractPlaylistsAction
} }
return $response->withJson( return $response->withJson(
new Entity\Api\StationPlaylistImportResult( new StationPlaylistImportResult(
true, true,
sprintf( sprintf(
__('Playlist successfully imported; %d of %d files were successfully matched.'), __('Playlist successfully imported; %d of %d files were successfully matched.'),

View File

@ -4,20 +4,21 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Entity\Enums\PlaylistOrders;
use App\Entity\Enums\PlaylistSources;
use App\Entity\Repository\StationPlaylistMediaRepository;
use App\Entity\Repository\StationPlaylistRepository;
use App\Exception; use App\Exception;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class PutOrderAction extends AbstractPlaylistsAction final class PutOrderAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationPlaylistRepository $playlistRepo,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo, private readonly StationPlaylistMediaRepository $spmRepo
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -26,11 +27,11 @@ final class PutOrderAction extends AbstractPlaylistsAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
if ( if (
Entity\Enums\PlaylistSources::Songs !== $record->getSourceEnum() PlaylistSources::Songs !== $record->getSourceEnum()
|| Entity\Enums\PlaylistOrders::Sequential !== $record->getOrderEnum() || PlaylistOrders::Sequential !== $record->getOrderEnum()
) { ) {
throw new Exception(__('This playlist is not a sequential playlist.')); throw new Exception(__('This playlist is not a sequential playlist.'));
} }

View File

@ -4,19 +4,19 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Entity\Api\Status;
use App\Entity\Repository\StationPlaylistMediaRepository;
use App\Entity\Repository\StationPlaylistRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class ReshuffleAction extends AbstractPlaylistsAction final class ReshuffleAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationPlaylistRepository $playlistRepo,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo, private readonly StationPlaylistMediaRepository $spmRepo
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -25,12 +25,12 @@ final class ReshuffleAction extends AbstractPlaylistsAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
$this->spmRepo->resetQueue($record); $this->spmRepo->resetQueue($record);
return $response->withJson( return $response->withJson(
new Entity\Api\Status( new Status(
true, true,
__('Playlist reshuffled.') __('Playlist reshuffled.')
) )

View File

@ -4,31 +4,38 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Playlists; namespace App\Controller\Api\Stations\Playlists;
use App\Entity; use App\Entity\Api\Status;
use App\Entity\Repository\StationPlaylistRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class ToggleAction extends AbstractPlaylistsAction final class ToggleAction
{ {
public function __construct(
private readonly StationPlaylistRepository $playlistRepo
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->playlistRepo->requireForStation($id, $request->getStation());
$new_value = !$record->getIsEnabled(); $new_value = !$record->getIsEnabled();
$record->setIsEnabled($new_value); $record->setIsEnabled($new_value);
$this->em->persist($record);
$this->em->flush(); $em = $this->playlistRepo->getEntityManager();
$em->persist($record);
$em->flush();
$flash_message = ($new_value) $flash_message = ($new_value)
? __('Playlist enabled.') ? __('Playlist enabled.')
: __('Playlist disabled.'); : __('Playlist disabled.');
return $response->withJson(new Entity\Api\Status(true, $flash_message)); return $response->withJson(new Status(true, $flash_message));
} }
} }

View File

@ -126,7 +126,7 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $station_id, string $station_id,
int $broadcast_id string $broadcast_id
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$broadcast = $this->getRecord($station, $broadcast_id); $broadcast = $this->getRecord($station, $broadcast_id);
@ -158,7 +158,7 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $station_id, string $station_id,
int $broadcast_id string $broadcast_id
): ResponseInterface { ): ResponseInterface {
$station = $request->getStation(); $station = $request->getStation();
$broadcast = $this->getRecord($station, $broadcast_id); $broadcast = $this->getRecord($station, $broadcast_id);

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Waveform; namespace App\Controller\Api\Stations\Waveform;
use App\Entity\Api\Error;
use App\Entity\Repository\StationMediaRepository; use App\Entity\Repository\StationMediaRepository;
use App\Entity\StationMedia; use App\Entity\StationMedia;
use App\Flysystem\StationFilesystems; use App\Flysystem\StationFilesystems;
@ -41,10 +40,7 @@ final class GetWaveformAction
} }
} }
$media = $this->mediaRepo->findByUniqueId($media_id, $station); $media = $this->mediaRepo->requireByUniqueId($media_id, $station);
if (!($media instanceof StationMedia)) {
return $response->withStatus(500)->withJson(new Error(500, 'Media not found.'));
}
$waveformPath = StationMedia::getWaveformPath($media->getUniqueId()); $waveformPath = StationMedia::getWaveformPath($media->getUniqueId());
if (!$fsMedia->fileExists($waveformPath)) { if (!$fsMedia->fileExists($waveformPath)) {

View File

@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Controller\Api\Stations\Webhooks;
use App\Entity;
use App\Exception\NotFoundException;
use Doctrine\ORM\EntityManagerInterface;
abstract class AbstractWebhooksAction
{
public function __construct(
protected EntityManagerInterface $em
) {
}
protected function requireRecord(Entity\Station $station, int|string $id): Entity\StationWebhook
{
$record = $this->em->getRepository(Entity\StationWebhook::class)->findOneBy(
[
'station' => $station,
'id' => (int)$id,
]
);
if (!$record instanceof Entity\StationWebhook) {
throw new NotFoundException(__('Web hook not found.'));
}
return $record;
}
}

View File

@ -4,21 +4,20 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Webhooks; namespace App\Controller\Api\Stations\Webhooks;
use App\Entity\Repository\StationWebhookRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Message\TestWebhookMessage; use App\Message\TestWebhookMessage;
use App\Utilities\File; use App\Utilities\File;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Messenger\MessageBus; use Symfony\Component\Messenger\MessageBus;
final class TestAction extends AbstractWebhooksAction final class TestAction
{ {
public function __construct( public function __construct(
EntityManagerInterface $em, private readonly StationWebhookRepository $webhookRepo,
private readonly MessageBus $messageBus private readonly MessageBus $messageBus
) { ) {
parent::__construct($em);
} }
public function __invoke( public function __invoke(
@ -27,7 +26,7 @@ final class TestAction extends AbstractWebhooksAction
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$webhook = $this->requireRecord($request->getStation(), $id); $webhook = $this->webhookRepo->requireForStation($id, $request->getStation());
$tempFile = File::generateTempPath('webhook_test_' . $id . '.log'); $tempFile = File::generateTempPath('webhook_test_' . $id . '.log');
touch($tempFile); touch($tempFile);

View File

@ -6,15 +6,21 @@ namespace App\Controller\Api\Stations\Webhooks;
use App\Controller\Api\Traits\HasLogViewer; use App\Controller\Api\Traits\HasLogViewer;
use App\Entity; use App\Entity;
use App\Entity\Repository\StationWebhookRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use App\Utilities\File; use App\Utilities\File;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class TestLogAction extends AbstractWebhooksAction final class TestLogAction
{ {
use HasLogViewer; use HasLogViewer;
public function __construct(
private readonly StationWebhookRepository $webhookRepo
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
@ -22,7 +28,7 @@ final class TestLogAction extends AbstractWebhooksAction
string $id, string $id,
string $path string $path
): ResponseInterface { ): ResponseInterface {
$this->requireRecord($request->getStation(), $id); $this->webhookRepo->requireForStation($id, $request->getStation());
$logPathPortion = 'webhook_test_' . $id; $logPathPortion = 'webhook_test_' . $id;
if (!str_contains($path, $logPathPortion)) { if (!str_contains($path, $logPathPortion)) {

View File

@ -5,25 +5,32 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Webhooks; namespace App\Controller\Api\Stations\Webhooks;
use App\Entity; use App\Entity;
use App\Entity\Repository\StationWebhookRepository;
use App\Http\Response; use App\Http\Response;
use App\Http\ServerRequest; use App\Http\ServerRequest;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
final class ToggleAction extends AbstractWebhooksAction final class ToggleAction
{ {
public function __construct(
private readonly StationWebhookRepository $webhookRepo
) {
}
public function __invoke( public function __invoke(
ServerRequest $request, ServerRequest $request,
Response $response, Response $response,
string $station_id, string $station_id,
string $id string $id
): ResponseInterface { ): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id); $record = $this->webhookRepo->requireForStation($id, $request->getStation());
$newValue = !$record->getIsEnabled(); $newValue = !$record->getIsEnabled();
$record->setIsEnabled($newValue); $record->setIsEnabled($newValue);
$this->em->persist($record); $em = $this->webhookRepo->getEntityManager();
$this->em->flush(); $em->persist($record);
$em->flush();
$flash_message = ($newValue) $flash_message = ($newValue)
? __('Web hook enabled.') ? __('Web hook enabled.')

View File

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace App; namespace App;
use App\Assets\AssetFactory; use App\Assets\BackgroundCustomAsset;
use App\Assets\BrowserIconCustomAsset;
use App\Entity; use App\Entity;
use App\Enums\SupportedLocales; use App\Enums\SupportedLocales;
use App\Enums\SupportedThemes; use App\Enums\SupportedThemes;
@ -108,7 +109,7 @@ class Customization
{ {
$publicCss = $this->settings->getPublicCustomCss() ?? ''; $publicCss = $this->settings->getPublicCustomCss() ?? '';
$background = AssetFactory::createBackground($this->environment); $background = new BackgroundCustomAsset();
if ($background->isUploaded()) { if ($background->isUploaded()) {
$backgroundUrl = $background->getUrl(); $backgroundUrl = $background->getUrl();
@ -140,7 +141,7 @@ class Customization
public function getBrowserIconUrl(int $size = 256): string public function getBrowserIconUrl(int $size = 256): string
{ {
return AssetFactory::createBrowserIcon($this->environment)->getUrlForSize($size); return (new BrowserIconCustomAsset())->getUrlForSize($size);
} }
/** /**

View File

@ -4,14 +4,9 @@ declare(strict_types=1);
namespace App\Doctrine; namespace App\Doctrine;
use App\Environment;
use App\Exception\NotFoundException; use App\Exception\NotFoundException;
use Azura\Normalizer\DoctrineEntityNormalizer;
use Closure; use Closure;
use Doctrine\Persistence\ObjectRepository; use Doctrine\Persistence\ObjectRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
/** /**
* @template TEntity as object * @template TEntity as object
@ -25,10 +20,7 @@ class Repository
protected ObjectRepository $repository; protected ObjectRepository $repository;
public function __construct( public function __construct(
protected ReloadableEntityManagerInterface $em, protected ReloadableEntityManagerInterface $em
protected Serializer $serializer,
protected Environment $environment,
protected LoggerInterface $logger
) { ) {
if (!isset($this->entityClass)) { if (!isset($this->entityClass)) {
/** @var class-string<TEntity> $defaultClass */ /** @var class-string<TEntity> $defaultClass */
@ -135,42 +127,4 @@ class Repository
return $select; return $select;
} }
/**
* FromArray (A Doctrine 1 Classic)
*
* @param object $entity
* @param array $source
*/
public function fromArray(object $entity, array $source): object
{
return $this->serializer->denormalize(
$source,
get_class($entity),
null,
[
AbstractNormalizer::OBJECT_TO_POPULATE => $entity,
]
);
}
/**
* ToArray (A Doctrine 1 Classic)
*
* @param object $entity
* @param bool $deep Iterate through collections associated with this item.
* @param bool $form_mode Return values in a format suitable for ZendForm setDefault function.
*
* @return mixed[]
*/
public function toArray(object $entity, bool $deep = false, bool $form_mode = false): array
{
return (array)$this->serializer->normalize(
$entity,
null,
[
DoctrineEntityNormalizer::NORMALIZE_TO_IDENTIFIERS => $form_mode,
]
);
}
} }

View File

@ -13,7 +13,7 @@ use DateTimeInterface;
/** /**
* @extends Repository<Entity\Analytics> * @extends Repository<Entity\Analytics>
*/ */
class AnalyticsRepository extends Repository final class AnalyticsRepository extends Repository
{ {
/** /**
* @return mixed[] * @return mixed[]

View File

@ -9,6 +9,6 @@ use App\Entity;
/** /**
* @extends AbstractSplitTokenRepository<Entity\ApiKey> * @extends AbstractSplitTokenRepository<Entity\ApiKey>
*/ */
class ApiKeyRepository extends AbstractSplitTokenRepository final class ApiKeyRepository extends AbstractSplitTokenRepository
{ {
} }

View File

@ -10,7 +10,7 @@ use App\Entity;
/** /**
* @extends Repository<Entity\CustomField> * @extends Repository<Entity\CustomField>
*/ */
class CustomFieldRepository extends Repository final class CustomFieldRepository extends Repository
{ {
/** /**
* @return Entity\CustomField[] * @return Entity\CustomField[]

View File

@ -7,37 +7,32 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Service\DeviceDetector; use App\Service\DeviceDetector;
use App\Service\IpGeolocation; use App\Service\IpGeolocation;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use DateTimeInterface; use DateTimeInterface;
use Doctrine\DBAL\Connection; use Doctrine\DBAL\Connection;
use Monolog\Registry;
use NowPlaying\Result\Client; use NowPlaying\Result\Client;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
use Throwable; use Throwable;
/** /**
* @extends Repository<Entity\Listener> * @extends Repository<Entity\Listener>
*/ */
class ListenerRepository extends Repository final class ListenerRepository extends Repository
{ {
use Entity\Traits\TruncateStrings; use Entity\Traits\TruncateStrings;
protected string $tableName; private string $tableName;
protected Connection $conn; private Connection $conn;
public function __construct( public function __construct(
protected DeviceDetector $deviceDetector,
protected IpGeolocation $ipGeolocation,
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly DeviceDetector $deviceDetector,
Environment $environment, private readonly IpGeolocation $ipGeolocation
LoggerInterface $logger
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
$this->tableName = $this->em->getClassMetadata(Entity\Listener::class)->getTableName(); $this->tableName = $this->em->getClassMetadata(Entity\Listener::class)->getTableName();
$this->conn = $this->em->getConnection(); $this->conn = $this->em->getConnection();
@ -190,7 +185,7 @@ class ListenerRepository extends Repository
$record['device_browser_family'] = $this->truncateNullableString($browserResult->browserFamily, 150); $record['device_browser_family'] = $this->truncateNullableString($browserResult->browserFamily, 150);
$record['device_os_family'] = $this->truncateNullableString($browserResult->osFamily, 150); $record['device_os_family'] = $this->truncateNullableString($browserResult->osFamily, 150);
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->error('Device Detector error: ' . $e->getMessage(), [ Registry::getInstance('app')->error('Device Detector error: ' . $e->getMessage(), [
'user_agent' => $userAgent, 'user_agent' => $userAgent,
'exception' => $e, 'exception' => $e,
]); ]);
@ -213,7 +208,7 @@ class ListenerRepository extends Repository
$record['location_lat'] = $ipInfo->lat; $record['location_lat'] = $ipInfo->lat;
$record['location_lon'] = $ipInfo->lon; $record['location_lon'] = $ipInfo->lon;
} catch (Throwable $e) { } catch (Throwable $e) {
$this->logger->error('IP Geolocation error: ' . $e->getMessage(), [ Registry::getInstance('app')->error('IP Geolocation error: ' . $e->getMessage(), [
'ip' => $ip, 'ip' => $ip,
'exception' => $e, 'exception' => $e,
]); ]);

View File

@ -7,7 +7,6 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Exception\InvalidPodcastMediaFileException; use App\Exception\InvalidPodcastMediaFileException;
use App\Exception\StorageLocationFullException; use App\Exception\StorageLocationFullException;
use App\Media\AlbumArt; use App\Media\AlbumArt;
@ -15,22 +14,17 @@ use App\Media\MetadataManager;
use Azura\Files\ExtendedFilesystemInterface; use Azura\Files\ExtendedFilesystemInterface;
use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToRetrieveMetadata;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends Repository<Entity\PodcastEpisode> * @extends Repository<Entity\PodcastEpisode>
*/ */
class PodcastEpisodeRepository extends Repository final class PodcastEpisodeRepository extends Repository
{ {
public function __construct( public function __construct(
protected MetadataManager $metadataManager,
ReloadableEntityManagerInterface $entityManager, ReloadableEntityManagerInterface $entityManager,
Serializer $serializer, private readonly MetadataManager $metadataManager
Environment $environment,
LoggerInterface $logger
) { ) {
parent::__construct($entityManager, $serializer, $environment, $logger); parent::__construct($entityManager);
} }
public function fetchEpisodeForStation(Entity\Station $station, string $episodeId): ?Entity\PodcastEpisode public function fetchEpisodeForStation(Entity\Station $station, string $episodeId): ?Entity\PodcastEpisode

View File

@ -7,28 +7,22 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Exception\StorageLocationFullException; use App\Exception\StorageLocationFullException;
use App\Media\AlbumArt; use App\Media\AlbumArt;
use Azura\Files\ExtendedFilesystemInterface; use Azura\Files\ExtendedFilesystemInterface;
use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToRetrieveMetadata;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends Repository<Entity\Podcast> * @extends Repository<Entity\Podcast>
*/ */
class PodcastRepository extends Repository final class PodcastRepository extends Repository
{ {
public function __construct( public function __construct(
ReloadableEntityManagerInterface $entityManager, ReloadableEntityManagerInterface $entityManager,
Serializer $serializer, private readonly PodcastEpisodeRepository $podcastEpisodeRepo,
Environment $environment,
LoggerInterface $logger,
protected PodcastEpisodeRepository $podcastEpisodeRepo,
) { ) {
parent::__construct($entityManager, $serializer, $environment, $logger); parent::__construct($entityManager);
} }
public function fetchPodcastForStation(Entity\Station $station, string $podcastId): ?Entity\Podcast public function fetchPodcastForStation(Entity\Station $station, string $podcastId): ?Entity\Podcast

View File

@ -11,7 +11,7 @@ use App\Enums\GlobalPermissions;
/** /**
* @extends Repository<Entity\RolePermission> * @extends Repository<Entity\RolePermission>
*/ */
class RolePermissionRepository extends Repository final class RolePermissionRepository extends Repository
{ {
/** /**
* @param Entity\Role $role * @param Entity\Role $role

View File

@ -10,6 +10,6 @@ use App\Entity;
/** /**
* @extends Repository<Entity\Role> * @extends Repository<Entity\Role>
*/ */
class RoleRepository extends Repository final class RoleRepository extends Repository
{ {
} }

View File

@ -7,31 +7,24 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Exception\ValidationException; use App\Exception\ValidationException;
use Psr\Log\LoggerInterface; use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Validator\Validator\ValidatorInterface;
/** /**
* @extends Repository<Entity\Settings> * @extends Repository<Entity\Settings>
*/ */
class SettingsRepository extends Repository final class SettingsRepository extends Repository
{ {
protected ValidatorInterface $validator;
protected string $entityClass = Entity\Settings::class; protected string $entityClass = Entity\Settings::class;
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly Serializer $serializer,
Environment $environment, private readonly ValidatorInterface $validator
LoggerInterface $logger,
ValidatorInterface $validator
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
$this->validator = $validator;
} }
public function readSettings(): Entity\Settings public function readSettings(): Entity\Settings
@ -78,4 +71,23 @@ class SettingsRepository extends Repository
$this->em->persist($settings); $this->em->persist($settings);
$this->em->flush(); $this->em->flush();
} }
public function fromArray(Entity\Settings $entity, array $source): Entity\Settings
{
return $this->serializer->denormalize(
$source,
Entity\Settings::class,
null,
[
AbstractNormalizer::OBJECT_TO_POPULATE => $entity,
]
);
}
public function toArray(Entity\Settings $entity): array
{
return (array)$this->serializer->normalize(
$entity
);
}
} }

View File

@ -6,28 +6,22 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity; use App\Entity;
use App\Environment;
use App\Radio\Backend\Liquidsoap\Command\FeedbackCommand; use App\Radio\Backend\Liquidsoap\Command\FeedbackCommand;
use App\Radio\Enums\BackendAdapters; use App\Radio\Enums\BackendAdapters;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends AbstractStationBasedRepository<Entity\SongHistory> * @extends AbstractStationBasedRepository<Entity\SongHistory>
*/ */
class SongHistoryRepository extends AbstractStationBasedRepository final class SongHistoryRepository extends AbstractStationBasedRepository
{ {
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly ListenerRepository $listenerRepository,
Environment $environment, private readonly StationQueueRepository $stationQueueRepository,
LoggerInterface $logger, private readonly FeedbackCommand $liquidsoapFeedback,
protected ListenerRepository $listenerRepository,
protected StationQueueRepository $stationQueueRepository,
protected FeedbackCommand $liquidsoapFeedback,
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
} }
/** /**

View File

@ -7,8 +7,8 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Exception\CannotProcessMediaException; use App\Exception\CannotProcessMediaException;
use App\Exception\NotFoundException;
use App\Media\AlbumArt; use App\Media\AlbumArt;
use App\Media\MetadataManager; use App\Media\MetadataManager;
use App\Media\RemoteAlbumArt; use App\Media\RemoteAlbumArt;
@ -17,8 +17,7 @@ use Azura\Files\ExtendedFilesystemInterface;
use Exception; use Exception;
use Generator; use Generator;
use League\Flysystem\FilesystemException; use League\Flysystem\FilesystemException;
use Psr\Log\LoggerInterface; use Monolog\Registry;
use Symfony\Component\Serializer\Serializer;
use const JSON_PRETTY_PRINT; use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR; use const JSON_THROW_ON_ERROR;
@ -27,21 +26,17 @@ use const JSON_UNESCAPED_SLASHES;
/** /**
* @extends Repository<Entity\StationMedia> * @extends Repository<Entity\StationMedia>
*/ */
class StationMediaRepository extends Repository final class StationMediaRepository extends Repository
{ {
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly MetadataManager $metadataManager,
Environment $environment, private readonly RemoteAlbumArt $remoteAlbumArt,
LoggerInterface $logger, private readonly CustomFieldRepository $customFieldRepo,
protected MetadataManager $metadataManager, private readonly StationPlaylistMediaRepository $spmRepo,
protected RemoteAlbumArt $remoteAlbumArt, private readonly UnprocessableMediaRepository $unprocessableMediaRepo
protected CustomFieldRepository $customFieldRepo,
protected StationPlaylistMediaRepository $spmRepo,
protected StorageLocationRepository $storageLocationRepo,
protected UnprocessableMediaRepository $unprocessableMediaRepo
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
} }
public function findForStation(int|string $id, Entity\Station $station): ?Entity\StationMedia public function findForStation(int|string $id, Entity\Station $station): ?Entity\StationMedia
@ -66,6 +61,15 @@ class StationMediaRepository extends Repository
return $media; return $media;
} }
public function requireForStation(int|string $id, Entity\Station $station): Entity\StationMedia
{
$record = $this->findForStation($id, $station);
if (null === $record) {
throw new NotFoundException();
}
return $record;
}
/** /**
* @param string $path * @param string $path
* @param Entity\Station|Entity\StorageLocation $source * @param Entity\Station|Entity\StorageLocation $source
@ -120,6 +124,17 @@ class StationMediaRepository extends Repository
return $media; return $media;
} }
public function requireByUniqueId(
string $uniqueId,
Entity\Station|Entity\StorageLocation $source
): Entity\StationMedia {
$record = $this->findByUniqueId($uniqueId, $source);
if (null === $record) {
throw new NotFoundException();
}
return $record;
}
protected function getStorageLocation(Entity\Station|Entity\StorageLocation $source): Entity\StorageLocation protected function getStorageLocation(Entity\Station|Entity\StorageLocation $source): Entity\StorageLocation
{ {
if ($source instanceof Entity\Station) { if ($source instanceof Entity\Station) {
@ -277,7 +292,7 @@ class StationMediaRepository extends Repository
try { try {
$this->writeAlbumArt($media, $artwork, $fs); $this->writeAlbumArt($media, $artwork, $fs);
} catch (Exception $exception) { } catch (Exception $exception) {
$this->logger->error( Registry::getInstance('app')->error(
sprintf( sprintf(
'Album Artwork for "%s" could not be processed: "%s"', 'Album Artwork for "%s" could not be processed: "%s"',
$filePath, $filePath,

View File

@ -12,7 +12,7 @@ use Azura\Files\ExtendedFilesystemInterface;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationMount> * @extends AbstractStationBasedRepository<Entity\StationMount>
*/ */
class StationMountRepository extends AbstractStationBasedRepository final class StationMountRepository extends AbstractStationBasedRepository
{ {
public function setIntro( public function setIntro(
Entity\StationMount $mount, Entity\StationMount $mount,

View File

@ -9,7 +9,7 @@ use App\Entity;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationPlaylistFolder> * @extends AbstractStationBasedRepository<Entity\StationPlaylistFolder>
*/ */
class StationPlaylistFolderRepository extends AbstractStationBasedRepository final class StationPlaylistFolderRepository extends AbstractStationBasedRepository
{ {
/** /**
* @param Entity\Station $station * @param Entity\Station $station

View File

@ -7,33 +7,23 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Doctrine\ORM\NoResultException; use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder; use Doctrine\ORM\QueryBuilder;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Log\LoggerInterface;
use RuntimeException; use RuntimeException;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends Repository<Entity\StationPlaylistMedia> * @extends Repository<Entity\StationPlaylistMedia>
*/ */
class StationPlaylistMediaRepository extends Repository final class StationPlaylistMediaRepository extends Repository
{ {
protected StationQueueRepository $queueRepo;
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly StationQueueRepository $queueRepo
Environment $environment,
LoggerInterface $logger,
StationQueueRepository $queueRepo
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
$this->queueRepo = $queueRepo;
} }
/** /**

View File

@ -9,7 +9,7 @@ use App\Entity;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationPlaylist> * @extends AbstractStationBasedRepository<Entity\StationPlaylist>
*/ */
class StationPlaylistRepository extends AbstractStationBasedRepository final class StationPlaylistRepository extends AbstractStationBasedRepository
{ {
/** /**
* @return Entity\StationPlaylist[] * @return Entity\StationPlaylist[]

View File

@ -13,7 +13,7 @@ use Doctrine\ORM\QueryBuilder;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationQueue> * @extends AbstractStationBasedRepository<Entity\StationQueue>
*/ */
class StationQueueRepository extends AbstractStationBasedRepository final class StationQueueRepository extends AbstractStationBasedRepository
{ {
public function clearForMediaAndPlaylist(Entity\StationMedia $media, Entity\StationPlaylist $playlist): void public function clearForMediaAndPlaylist(Entity\StationMedia $media, Entity\StationPlaylist $playlist): void
{ {

View File

@ -9,7 +9,7 @@ use App\Entity;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationRemote> * @extends AbstractStationBasedRepository<Entity\StationRemote>
*/ */
class StationRemoteRepository extends AbstractStationBasedRepository final class StationRemoteRepository extends AbstractStationBasedRepository
{ {
/** /**
* @param Entity\Station $station * @param Entity\Station $station

View File

@ -4,33 +4,27 @@ declare(strict_types=1);
namespace App\Entity\Repository; namespace App\Entity\Repository;
use App\Assets\AssetFactory; use App\Assets\AlbumArtCustomAsset;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Flysystem\StationFilesystems; use App\Flysystem\StationFilesystems;
use App\Radio\Frontend\AbstractFrontend; use App\Radio\Frontend\AbstractFrontend;
use App\Service\Flow\UploadedFile; use App\Service\Flow\UploadedFile;
use Azura\Files\ExtendedFilesystemInterface; use Azura\Files\ExtendedFilesystemInterface;
use Closure; use Closure;
use Psr\Http\Message\UriInterface; use Psr\Http\Message\UriInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends Repository<Entity\Station> * @extends Repository<Entity\Station>
*/ */
class StationRepository extends Repository final class StationRepository extends Repository
{ {
public function __construct( public function __construct(
protected SettingsRepository $settingsRepo,
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly SettingsRepository $settingsRepo
Environment $environment,
LoggerInterface $logger
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
} }
/** /**
@ -103,11 +97,8 @@ class StationRepository extends Repository
// Create default mountpoints if station supports them. // Create default mountpoints if station supports them.
if ($frontend_adapter->supportsMounts()) { if ($frontend_adapter->supportsMounts()) {
// Create default mount points. // Create default mount points.
foreach ($frontend_adapter->getDefaultMounts() as $mount_point) { foreach ($frontend_adapter->getDefaultMounts($station) as $mount) {
$mount_record = new Entity\StationMount($station); $this->em->persist($mount);
$this->fromArray($mount_record, $mount_point);
$this->em->persist($mount_record);
} }
} }
@ -165,7 +156,7 @@ class StationRepository extends Repository
} }
$customUrl = $this->settingsRepo->readSettings()->getDefaultAlbumArtUrlAsUri(); $customUrl = $this->settingsRepo->readSettings()->getDefaultAlbumArtUrlAsUri();
return $customUrl ?? AssetFactory::createAlbumArt($this->environment)->getUri(); return $customUrl ?? (new AlbumArtCustomAsset())->getUri();
} }
public function setFallback( public function setFallback(

View File

@ -6,32 +6,26 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity; use App\Entity;
use App\Environment;
use App\Exception; use App\Exception;
use App\Radio\AutoDJ; use App\Radio\AutoDJ;
use App\Radio\Frontend\Blocklist\BlocklistParser; use App\Radio\Frontend\Blocklist\BlocklistParser;
use App\Service\DeviceDetector; use App\Service\DeviceDetector;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationRequest> * @extends AbstractStationBasedRepository<Entity\StationRequest>
*/ */
class StationRequestRepository extends AbstractStationBasedRepository final class StationRequestRepository extends AbstractStationBasedRepository
{ {
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly StationMediaRepository $mediaRepo,
Environment $environment, private readonly DeviceDetector $deviceDetector,
LoggerInterface $logger, private readonly BlocklistParser $blocklistParser,
protected StationMediaRepository $mediaRepo, private readonly AutoDJ\DuplicatePrevention $duplicatePrevention,
protected DeviceDetector $deviceDetector,
protected BlocklistParser $blocklistParser,
protected AutoDJ\DuplicatePrevention $duplicatePrevention,
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
} }
public function getPendingRequest(int|string $id, Entity\Station $station): ?Entity\StationRequest public function getPendingRequest(int|string $id, Entity\Station $station): ?Entity\StationRequest

View File

@ -7,27 +7,21 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Doctrine\Repository; use App\Doctrine\Repository;
use App\Entity; use App\Entity;
use App\Environment;
use App\Radio\AutoDJ\Scheduler; use App\Radio\AutoDJ\Scheduler;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use Carbon\CarbonInterface; use Carbon\CarbonInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends Repository<Entity\StationSchedule> * @extends Repository<Entity\StationSchedule>
*/ */
class StationScheduleRepository extends Repository final class StationScheduleRepository extends Repository
{ {
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly Scheduler $scheduler,
Environment $environment, private readonly Entity\ApiGenerator\ScheduleApiGenerator $scheduleApiGenerator
LoggerInterface $logger,
protected Scheduler $scheduler,
protected Entity\ApiGenerator\ScheduleApiGenerator $scheduleApiGenerator
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
} }
/** /**

View File

@ -11,7 +11,7 @@ use Carbon\CarbonImmutable;
/** /**
* @extends Repository<Entity\StationStreamerBroadcast> * @extends Repository<Entity\StationStreamerBroadcast>
*/ */
class StationStreamerBroadcastRepository extends Repository final class StationStreamerBroadcastRepository extends Repository
{ {
public function getLatestBroadcast(Entity\Station $station): ?Entity\StationStreamerBroadcast public function getLatestBroadcast(Entity\Station $station): ?Entity\StationStreamerBroadcast
{ {
@ -58,7 +58,7 @@ class StationStreamerBroadcastRepository extends Repository
public function getActiveBroadcasts(Entity\Station $station): array public function getActiveBroadcasts(Entity\Station $station): array
{ {
return $this->repository->findBy([ return $this->repository->findBy([
'station' => $station, 'station' => $station,
'timestampEnd' => 0, 'timestampEnd' => 0,
]); ]);
} }

View File

@ -6,34 +6,21 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface; use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity; use App\Entity;
use App\Environment;
use App\Flysystem\StationFilesystems; use App\Flysystem\StationFilesystems;
use App\Media\AlbumArt; use App\Media\AlbumArt;
use App\Radio\AutoDJ\Scheduler; use App\Radio\AutoDJ\Scheduler;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/** /**
* @extends AbstractStationBasedRepository<Entity\StationStreamer> * @extends AbstractStationBasedRepository<Entity\StationStreamer>
*/ */
class StationStreamerRepository extends AbstractStationBasedRepository final class StationStreamerRepository extends AbstractStationBasedRepository
{ {
protected Scheduler $scheduler;
protected StationStreamerBroadcastRepository $broadcastRepo;
public function __construct( public function __construct(
ReloadableEntityManagerInterface $em, ReloadableEntityManagerInterface $em,
Serializer $serializer, private readonly Scheduler $scheduler,
Environment $environment, private readonly StationStreamerBroadcastRepository $broadcastRepo
LoggerInterface $logger,
Scheduler $scheduler,
StationStreamerBroadcastRepository $broadcastRepo
) { ) {
parent::__construct($em, $serializer, $environment, $logger); parent::__construct($em);
$this->scheduler = $scheduler;
$this->broadcastRepo = $broadcastRepo;
} }
/** /**

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace App\Entity\Repository;
use App\Entity;
/**
* @extends AbstractStationBasedRepository<Entity\StationWebhook>
*/
final class StationWebhookRepository extends AbstractStationBasedRepository
{
}

View File

@ -11,7 +11,7 @@ use Brick\Math\BigInteger;
/** /**
* @extends Repository<Entity\StorageLocation> * @extends Repository<Entity\StorageLocation>
*/ */
class StorageLocationRepository extends Repository final class StorageLocationRepository extends Repository
{ {
public function findByType( public function findByType(
string|Entity\Enums\StorageLocationTypes $type, string|Entity\Enums\StorageLocationTypes $type,
@ -24,7 +24,7 @@ class StorageLocationRepository extends Repository
return $this->repository->findOneBy( return $this->repository->findOneBy(
[ [
'type' => $type, 'type' => $type,
'id' => $id, 'id' => $id,
] ]
); );
} }

View File

@ -11,7 +11,7 @@ use Generator;
/** /**
* @extends Repository<Entity\UnprocessableMedia> * @extends Repository<Entity\UnprocessableMedia>
*/ */
class UnprocessableMediaRepository extends Repository final class UnprocessableMediaRepository extends Repository
{ {
public function findByPath(string $path, Entity\StorageLocation $storageLocation): ?Entity\UnprocessableMedia public function findByPath(string $path, Entity\StorageLocation $storageLocation): ?Entity\UnprocessableMedia
{ {

View File

@ -10,7 +10,7 @@ use App\Security\SplitToken;
/** /**
* @extends AbstractSplitTokenRepository<Entity\UserLoginToken> * @extends AbstractSplitTokenRepository<Entity\UserLoginToken>
*/ */
class UserLoginTokenRepository extends AbstractSplitTokenRepository final class UserLoginTokenRepository extends AbstractSplitTokenRepository
{ {
public function createToken(Entity\User $user): SplitToken public function createToken(Entity\User $user): SplitToken
{ {

View File

@ -10,7 +10,7 @@ use App\Entity;
/** /**
* @extends Repository<Entity\User> * @extends Repository<Entity\User>
*/ */
class UserRepository extends Repository final class UserRepository extends Repository
{ {
public function findByEmail(string $email): ?Entity\User public function findByEmail(string $email): ?Entity\User
{ {

View File

@ -8,6 +8,7 @@ use App\Entity;
use App\Environment; use App\Environment;
use App\Http\Router; use App\Http\Router;
use App\Radio\AbstractAdapter; use App\Radio\AbstractAdapter;
use App\Radio\Enums\StreamFormats;
use App\Xml\Reader; use App\Xml\Reader;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Exception; use Exception;
@ -50,19 +51,18 @@ abstract class AbstractFrontend extends AbstractAdapter
/** /**
* Get the default mounts when resetting or initializing a station. * Get the default mounts when resetting or initializing a station.
* *
* @return mixed[] * @return Entity\StationMount[]
*/ */
public function getDefaultMounts(): array public function getDefaultMounts(Entity\Station $station): array
{ {
return [ $record = new Entity\StationMount($station);
[ $record->setName('/radio.mp3');
'name' => '/radio.mp3', $record->setIsDefault(true);
'is_default' => 1, $record->setEnableAutodj(true);
'enable_autodj' => 1, $record->setAutodjFormat(StreamFormats::Mp3->value);
'autodj_format' => 'mp3', $record->setAutodjBitrate(128);
'autodj_bitrate' => 128,
], return [$record];
];
} }
/** /**