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
{
public function __construct(
protected Environment $environment
) {
}
abstract protected function getPattern(): string;
abstract protected function getDefaultUrl(): string;
@ -22,7 +17,7 @@ abstract class AbstractCustomAsset implements CustomAssetInterface
public function getPath(): string
{
$pattern = sprintf($this->getPattern(), '');
return $this->environment->getUploadsDirectory() . '/' . $pattern;
return Environment::getInstance()->getUploadsDirectory() . '/' . $pattern;
}
public function getUrl(): string
@ -32,7 +27,7 @@ abstract class AbstractCustomAsset implements CustomAssetInterface
$pattern = $this->getPattern();
$mtime = filemtime($path);
return $this->environment->getAssetUrl() . self::UPLOADS_URL_PREFIX . '/' . sprintf(
return Environment::getInstance()->getAssetUrl() . self::UPLOADS_URL_PREFIX . '/' . sprintf(
$pattern,
'.' . $mtime
);

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Assets;
use App\Environment;
use Intervention\Image\Constraint;
use Intervention\Image\Image;
@ -16,7 +17,7 @@ class AlbumArtCustomAsset extends AbstractCustomAsset
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

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;
use App\Environment;
use Intervention\Image\Constraint;
use Intervention\Image\Image;
@ -16,7 +17,7 @@ class BackgroundCustomAsset extends AbstractCustomAsset
protected function getDefaultUrl(): string
{
return $this->environment->getAssetUrl() . '/img/hexbg.png';
return Environment::getInstance()->getAssetUrl() . '/img/hexbg.png';
}
public function upload(Image $image): void

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Assets;
use App\Environment;
use Intervention\Image\Image;
use Symfony\Component\Filesystem\Filesystem;
@ -34,13 +35,15 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
protected function getDefaultUrl(): string
{
$assetUrl = $this->environment->getAssetUrl();
return $assetUrl . '/icons/' . $this->environment->getAppEnvironmentEnum()->value . '/original.png';
$env = Environment::getInstance();
$assetUrl = $env->getAssetUrl();
return $assetUrl . '/icons/' . $env->getAppEnvironmentEnum()->value . '/original.png';
}
public function upload(Image $image): void
{
$uploadsDir = $this->environment->getUploadsDirectory() . '/browser_icon';
$uploadsDir = Environment::getInstance()->getUploadsDirectory() . '/browser_icon';
(new Filesystem())->mkdir($uploadsDir);
$newImage = clone $image;
@ -56,15 +59,16 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
public function delete(): void
{
$uploadsDir = $this->environment->getUploadsDirectory() . '/browser_icon';
$uploadsDir = Environment::getInstance()->getUploadsDirectory() . '/browser_icon';
(new Filesystem())->remove($uploadsDir);
}
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';
if (is_file($iconPath)) {
@ -72,6 +76,6 @@ class BrowserIconCustomAsset extends AbstractCustomAsset
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;
use App\Assets\AssetFactory;
use App\Assets\AssetTypes;
use App\Entity\Settings;
use App\Http\Response;
use App\Http\ServerRequest;
@ -28,13 +28,13 @@ final class BrandingAction
'group' => Settings::GROUP_BRANDING,
]),
'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', [
'type' => AssetFactory::TYPE_BACKGROUND,
'type' => AssetTypes::Background->value,
]),
'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(
ServerRequest $request,
Response $response,
string|int $station_id,
string $station_id,
string $log
): ResponseInterface {
if ('global' === $station_id) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -47,13 +47,8 @@ final class DeleteIntroAction
string $id
): ResponseInterface {
$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);
return $response->withJson(Entity\Api\Status::deleted());

View File

@ -55,12 +55,7 @@ final class PostIntroAction
}
if (null !== $id) {
$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->setIntro($mount, $flowResponse);
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.')));
}
$media = $this->mediaRepo->findByUniqueId($media_id, $station);
if (!($media instanceof Entity\StationMedia)) {
return $response->withStatus(404)
->withJson(Entity\Api\Error::notFound());
}
$media = $this->mediaRepo->requireByUniqueId($media_id, $station);
$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;
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\ServerRequest;
use DeepCopy;
use Doctrine\Common\Collections\Collection;
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(
ServerRequest $request,
Response $response,
string $station_id,
string $id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record = $this->playlistRepo->requireForStation($id, $request->getStation());
$data = (array)$request->getParsedBody();
@ -43,10 +55,10 @@ final class CloneAction extends AbstractPlaylistsAction
);
$copier->addFilter(
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->setName($data['name'] ?? ($record->getName() . ' - Copy'));
@ -57,7 +69,7 @@ final class CloneAction extends AbstractPlaylistsAction
if (in_array('schedule', $toClone, true)) {
foreach ($record->getScheduleItems() as $oldScheduleItem) {
/** @var Entity\StationSchedule $newScheduleItem */
/** @var StationSchedule $newScheduleItem */
$newScheduleItem = $copier->copy($oldScheduleItem);
$newScheduleItem->setPlaylist($newRecord);
@ -67,14 +79,14 @@ final class CloneAction extends AbstractPlaylistsAction
if (in_array('media', $toClone, true)) {
foreach ($record->getFolders() as $oldPlaylistFolder) {
/** @var Entity\StationPlaylistFolder $newPlaylistFolder */
/** @var StationPlaylistFolder $newPlaylistFolder */
$newPlaylistFolder = $copier->copy($oldPlaylistFolder);
$newPlaylistFolder->setPlaylist($newRecord);
$this->em->persist($newPlaylistFolder);
}
foreach ($record->getMediaItems() as $oldMediaItem) {
/** @var Entity\StationPlaylistMedia $newMediaItem */
/** @var StationPlaylistMedia $newMediaItem */
$newMediaItem = $copier->copy($oldMediaItem);
$newMediaItem->setPlaylist($newRecord);
@ -84,6 +96,6 @@ final class CloneAction extends AbstractPlaylistsAction
$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;
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\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
final class DeleteQueueAction extends AbstractPlaylistsAction
final class DeleteQueueAction
{
public function __construct(
EntityManagerInterface $em,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo,
private readonly StationPlaylistRepository $playlistRepo,
private readonly StationPlaylistMediaRepository $spmRepo,
) {
parent::__construct($em);
}
public function __invoke(
@ -25,12 +25,12 @@ final class DeleteQueueAction extends AbstractPlaylistsAction
string $station_id,
string $id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record = $this->playlistRepo->requireForStation($id, $request->getStation());
$this->spmRepo->resetQueue($record);
return $response->withJson(
new Entity\Api\Status(
new Status(
true,
__('Playlist queue cleared.')
)

View File

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

View File

@ -4,14 +4,23 @@ declare(strict_types=1);
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\Http\Response;
use App\Http\ServerRequest;
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(
ServerRequest $request,
Response $response,
@ -19,11 +28,11 @@ final class GetOrderAction extends AbstractPlaylistsAction
string $id
): ResponseInterface {
$station = $request->getStation();
$record = $this->requireRecord($station, $id);
$record = $this->playlistRepo->requireForStation($id, $station);
if (
Entity\Enums\PlaylistSources::Songs !== $record->getSourceEnum()
|| Entity\Enums\PlaylistOrders::Sequential !== $record->getOrderEnum()
PlaylistSources::Songs !== $record->getSourceEnum()
|| PlaylistOrders::Sequential !== $record->getOrderEnum()
) {
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;
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\ServerRequest;
use App\Paginator;
use Doctrine\ORM\EntityManagerInterface;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
final class GetQueueAction extends AbstractPlaylistsAction
final class GetQueueAction
{
public function __construct(
EntityManagerInterface $em,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo
private readonly StationPlaylistRepository $playlistRepo,
private readonly StationPlaylistMediaRepository $spmRepo
) {
parent::__construct($em);
}
public function __invoke(
@ -27,13 +28,13 @@ final class GetQueueAction extends AbstractPlaylistsAction
string $station_id,
string $id
): 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.');
}
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.');
}

View File

@ -4,22 +4,26 @@ declare(strict_types=1);
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\ServerRequest;
use App\Radio\PlaylistParser;
use App\Utilities\File;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\UploadedFileInterface;
final class ImportAction extends AbstractPlaylistsAction
final class ImportAction
{
public function __construct(
EntityManagerInterface $em,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo,
private readonly StationPlaylistRepository $playlistRepo,
private readonly StationPlaylistMediaRepository $spmRepo,
private readonly ReloadableEntityManagerInterface $em,
) {
parent::__construct($em);
}
public function __invoke(
@ -28,13 +32,13 @@ final class ImportAction extends AbstractPlaylistsAction
string $station_id,
string $id
): ResponseInterface {
$playlist = $this->requireRecord($request->getStation(), $id);
$playlist = $this->playlistRepo->requireForStation($id, $request->getStation());
$files = $request->getUploadedFiles();
if (empty($files['playlist_file'])) {
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 */
@ -42,7 +46,7 @@ final class ImportAction extends AbstractPlaylistsAction
if (UPLOAD_ERR_OK !== $file->getError()) {
return $response->withStatus(500)
->withJson(Entity\Api\Error::fromFileError($file->getError()));
->withJson(Error::fromFileError($file->getError()));
}
$playlistFile = $file->getStream()->getContents();
@ -137,10 +141,10 @@ final class ImportAction extends AbstractPlaylistsAction
->setParameter('matched_ids', $matches)
->execute();
/** @var Entity\StationMedia[] $mediaById */
/** @var StationMedia[] $mediaById */
$mediaById = [];
foreach ($matchedMediaRaw as $row) {
/** @var Entity\StationMedia $row */
/** @var StationMedia $row */
$mediaById[$row->getId()] = $row;
}
@ -161,7 +165,7 @@ final class ImportAction extends AbstractPlaylistsAction
}
return $response->withJson(
new Entity\Api\StationPlaylistImportResult(
new StationPlaylistImportResult(
true,
sprintf(
__('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;
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\Http\Response;
use App\Http\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
final class PutOrderAction extends AbstractPlaylistsAction
final class PutOrderAction
{
public function __construct(
EntityManagerInterface $em,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo,
private readonly StationPlaylistRepository $playlistRepo,
private readonly StationPlaylistMediaRepository $spmRepo
) {
parent::__construct($em);
}
public function __invoke(
@ -26,11 +27,11 @@ final class PutOrderAction extends AbstractPlaylistsAction
string $station_id,
string $id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record = $this->playlistRepo->requireForStation($id, $request->getStation());
if (
Entity\Enums\PlaylistSources::Songs !== $record->getSourceEnum()
|| Entity\Enums\PlaylistOrders::Sequential !== $record->getOrderEnum()
PlaylistSources::Songs !== $record->getSourceEnum()
|| PlaylistOrders::Sequential !== $record->getOrderEnum()
) {
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;
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\ServerRequest;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
final class ReshuffleAction extends AbstractPlaylistsAction
final class ReshuffleAction
{
public function __construct(
EntityManagerInterface $em,
private readonly Entity\Repository\StationPlaylistMediaRepository $spmRepo,
private readonly StationPlaylistRepository $playlistRepo,
private readonly StationPlaylistMediaRepository $spmRepo
) {
parent::__construct($em);
}
public function __invoke(
@ -25,12 +25,12 @@ final class ReshuffleAction extends AbstractPlaylistsAction
string $station_id,
string $id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record = $this->playlistRepo->requireForStation($id, $request->getStation());
$this->spmRepo->resetQueue($record);
return $response->withJson(
new Entity\Api\Status(
new Status(
true,
__('Playlist reshuffled.')
)

View File

@ -4,31 +4,38 @@ declare(strict_types=1);
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\ServerRequest;
use Psr\Http\Message\ResponseInterface;
final class ToggleAction extends AbstractPlaylistsAction
final class ToggleAction
{
public function __construct(
private readonly StationPlaylistRepository $playlistRepo
) {
}
public function __invoke(
ServerRequest $request,
Response $response,
string $station_id,
string $id
): ResponseInterface {
$record = $this->requireRecord($request->getStation(), $id);
$record = $this->playlistRepo->requireForStation($id, $request->getStation());
$new_value = !$record->getIsEnabled();
$record->setIsEnabled($new_value);
$this->em->persist($record);
$this->em->flush();
$em = $this->playlistRepo->getEntityManager();
$em->persist($record);
$em->flush();
$flash_message = ($new_value)
? __('Playlist enabled.')
: __('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,
Response $response,
string $station_id,
int $broadcast_id
string $broadcast_id
): ResponseInterface {
$station = $request->getStation();
$broadcast = $this->getRecord($station, $broadcast_id);
@ -158,7 +158,7 @@ final class BroadcastsController extends AbstractApiCrudController
ServerRequest $request,
Response $response,
string $station_id,
int $broadcast_id
string $broadcast_id
): ResponseInterface {
$station = $request->getStation();
$broadcast = $this->getRecord($station, $broadcast_id);

View File

@ -4,7 +4,6 @@ declare(strict_types=1);
namespace App\Controller\Api\Stations\Waveform;
use App\Entity\Api\Error;
use App\Entity\Repository\StationMediaRepository;
use App\Entity\StationMedia;
use App\Flysystem\StationFilesystems;
@ -41,10 +40,7 @@ final class GetWaveformAction
}
}
$media = $this->mediaRepo->findByUniqueId($media_id, $station);
if (!($media instanceof StationMedia)) {
return $response->withStatus(500)->withJson(new Error(500, 'Media not found.'));
}
$media = $this->mediaRepo->requireByUniqueId($media_id, $station);
$waveformPath = StationMedia::getWaveformPath($media->getUniqueId());
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;
use App\Entity\Repository\StationWebhookRepository;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Message\TestWebhookMessage;
use App\Utilities\File;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\Messenger\MessageBus;
final class TestAction extends AbstractWebhooksAction
final class TestAction
{
public function __construct(
EntityManagerInterface $em,
private readonly StationWebhookRepository $webhookRepo,
private readonly MessageBus $messageBus
) {
parent::__construct($em);
}
public function __invoke(
@ -27,7 +26,7 @@ final class TestAction extends AbstractWebhooksAction
string $station_id,
string $id
): ResponseInterface {
$webhook = $this->requireRecord($request->getStation(), $id);
$webhook = $this->webhookRepo->requireForStation($id, $request->getStation());
$tempFile = File::generateTempPath('webhook_test_' . $id . '.log');
touch($tempFile);

View File

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

View File

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

View File

@ -4,7 +4,8 @@ declare(strict_types=1);
namespace App;
use App\Assets\AssetFactory;
use App\Assets\BackgroundCustomAsset;
use App\Assets\BrowserIconCustomAsset;
use App\Entity;
use App\Enums\SupportedLocales;
use App\Enums\SupportedThemes;
@ -108,7 +109,7 @@ class Customization
{
$publicCss = $this->settings->getPublicCustomCss() ?? '';
$background = AssetFactory::createBackground($this->environment);
$background = new BackgroundCustomAsset();
if ($background->isUploaded()) {
$backgroundUrl = $background->getUrl();
@ -140,7 +141,7 @@ class Customization
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;
use App\Environment;
use App\Exception\NotFoundException;
use Azura\Normalizer\DoctrineEntityNormalizer;
use Closure;
use Doctrine\Persistence\ObjectRepository;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
/**
* @template TEntity as object
@ -25,10 +20,7 @@ class Repository
protected ObjectRepository $repository;
public function __construct(
protected ReloadableEntityManagerInterface $em,
protected Serializer $serializer,
protected Environment $environment,
protected LoggerInterface $logger
protected ReloadableEntityManagerInterface $em
) {
if (!isset($this->entityClass)) {
/** @var class-string<TEntity> $defaultClass */
@ -135,42 +127,4 @@ class Repository
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>
*/
class AnalyticsRepository extends Repository
final class AnalyticsRepository extends Repository
{
/**
* @return mixed[]

View File

@ -9,6 +9,6 @@ use App\Entity;
/**
* @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>
*/
class CustomFieldRepository extends Repository
final class CustomFieldRepository extends Repository
{
/**
* @return Entity\CustomField[]

View File

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

View File

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

View File

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

View File

@ -10,6 +10,6 @@ use App\Entity;
/**
* @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\Repository;
use App\Entity;
use App\Environment;
use App\Exception\ValidationException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* @extends Repository<Entity\Settings>
*/
class SettingsRepository extends Repository
final class SettingsRepository extends Repository
{
protected ValidatorInterface $validator;
protected string $entityClass = Entity\Settings::class;
public function __construct(
ReloadableEntityManagerInterface $em,
Serializer $serializer,
Environment $environment,
LoggerInterface $logger,
ValidatorInterface $validator
private readonly Serializer $serializer,
private readonly ValidatorInterface $validator
) {
parent::__construct($em, $serializer, $environment, $logger);
$this->validator = $validator;
parent::__construct($em);
}
public function readSettings(): Entity\Settings
@ -78,4 +71,23 @@ class SettingsRepository extends Repository
$this->em->persist($settings);
$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\Entity;
use App\Environment;
use App\Radio\Backend\Liquidsoap\Command\FeedbackCommand;
use App\Radio\Enums\BackendAdapters;
use Carbon\CarbonImmutable;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/**
* @extends AbstractStationBasedRepository<Entity\SongHistory>
*/
class SongHistoryRepository extends AbstractStationBasedRepository
final class SongHistoryRepository extends AbstractStationBasedRepository
{
public function __construct(
ReloadableEntityManagerInterface $em,
Serializer $serializer,
Environment $environment,
LoggerInterface $logger,
protected ListenerRepository $listenerRepository,
protected StationQueueRepository $stationQueueRepository,
protected FeedbackCommand $liquidsoapFeedback,
private readonly ListenerRepository $listenerRepository,
private readonly StationQueueRepository $stationQueueRepository,
private readonly 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\Repository;
use App\Entity;
use App\Environment;
use App\Exception\CannotProcessMediaException;
use App\Exception\NotFoundException;
use App\Media\AlbumArt;
use App\Media\MetadataManager;
use App\Media\RemoteAlbumArt;
@ -17,8 +17,7 @@ use Azura\Files\ExtendedFilesystemInterface;
use Exception;
use Generator;
use League\Flysystem\FilesystemException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
use Monolog\Registry;
use const JSON_PRETTY_PRINT;
use const JSON_THROW_ON_ERROR;
@ -27,21 +26,17 @@ use const JSON_UNESCAPED_SLASHES;
/**
* @extends Repository<Entity\StationMedia>
*/
class StationMediaRepository extends Repository
final class StationMediaRepository extends Repository
{
public function __construct(
ReloadableEntityManagerInterface $em,
Serializer $serializer,
Environment $environment,
LoggerInterface $logger,
protected MetadataManager $metadataManager,
protected RemoteAlbumArt $remoteAlbumArt,
protected CustomFieldRepository $customFieldRepo,
protected StationPlaylistMediaRepository $spmRepo,
protected StorageLocationRepository $storageLocationRepo,
protected UnprocessableMediaRepository $unprocessableMediaRepo
private readonly MetadataManager $metadataManager,
private readonly RemoteAlbumArt $remoteAlbumArt,
private readonly CustomFieldRepository $customFieldRepo,
private readonly StationPlaylistMediaRepository $spmRepo,
private readonly UnprocessableMediaRepository $unprocessableMediaRepo
) {
parent::__construct($em, $serializer, $environment, $logger);
parent::__construct($em);
}
public function findForStation(int|string $id, Entity\Station $station): ?Entity\StationMedia
@ -66,6 +61,15 @@ class StationMediaRepository extends Repository
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 Entity\Station|Entity\StorageLocation $source
@ -120,6 +124,17 @@ class StationMediaRepository extends Repository
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
{
if ($source instanceof Entity\Station) {
@ -277,7 +292,7 @@ class StationMediaRepository extends Repository
try {
$this->writeAlbumArt($media, $artwork, $fs);
} catch (Exception $exception) {
$this->logger->error(
Registry::getInstance('app')->error(
sprintf(
'Album Artwork for "%s" could not be processed: "%s"',
$filePath,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,32 +6,26 @@ namespace App\Entity\Repository;
use App\Doctrine\ReloadableEntityManagerInterface;
use App\Entity;
use App\Environment;
use App\Exception;
use App\Radio\AutoDJ;
use App\Radio\Frontend\Blocklist\BlocklistParser;
use App\Service\DeviceDetector;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/**
* @extends AbstractStationBasedRepository<Entity\StationRequest>
*/
class StationRequestRepository extends AbstractStationBasedRepository
final class StationRequestRepository extends AbstractStationBasedRepository
{
public function __construct(
ReloadableEntityManagerInterface $em,
Serializer $serializer,
Environment $environment,
LoggerInterface $logger,
protected StationMediaRepository $mediaRepo,
protected DeviceDetector $deviceDetector,
protected BlocklistParser $blocklistParser,
protected AutoDJ\DuplicatePrevention $duplicatePrevention,
private readonly StationMediaRepository $mediaRepo,
private readonly DeviceDetector $deviceDetector,
private readonly BlocklistParser $blocklistParser,
private readonly AutoDJ\DuplicatePrevention $duplicatePrevention,
) {
parent::__construct($em, $serializer, $environment, $logger);
parent::__construct($em);
}
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\Repository;
use App\Entity;
use App\Environment;
use App\Radio\AutoDJ\Scheduler;
use Carbon\CarbonImmutable;
use Carbon\CarbonInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\Serializer\Serializer;
/**
* @extends Repository<Entity\StationSchedule>
*/
class StationScheduleRepository extends Repository
final class StationScheduleRepository extends Repository
{
public function __construct(
ReloadableEntityManagerInterface $em,
Serializer $serializer,
Environment $environment,
LoggerInterface $logger,
protected Scheduler $scheduler,
protected Entity\ApiGenerator\ScheduleApiGenerator $scheduleApiGenerator
private readonly Scheduler $scheduler,
private readonly 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>
*/
class StationStreamerBroadcastRepository extends Repository
final class StationStreamerBroadcastRepository extends Repository
{
public function getLatestBroadcast(Entity\Station $station): ?Entity\StationStreamerBroadcast
{
@ -58,7 +58,7 @@ class StationStreamerBroadcastRepository extends Repository
public function getActiveBroadcasts(Entity\Station $station): array
{
return $this->repository->findBy([
'station' => $station,
'station' => $station,
'timestampEnd' => 0,
]);
}

View File

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

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,7 @@ use App\Entity;
use App\Environment;
use App\Http\Router;
use App\Radio\AbstractAdapter;
use App\Radio\Enums\StreamFormats;
use App\Xml\Reader;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
@ -50,19 +51,18 @@ abstract class AbstractFrontend extends AbstractAdapter
/**
* 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 [
[
'name' => '/radio.mp3',
'is_default' => 1,
'enable_autodj' => 1,
'autodj_format' => 'mp3',
'autodj_bitrate' => 128,
],
];
$record = new Entity\StationMount($station);
$record->setName('/radio.mp3');
$record->setIsDefault(true);
$record->setEnableAutodj(true);
$record->setAutodjFormat(StreamFormats::Mp3->value);
$record->setAutodjBitrate(128);
return [$record];
}
/**