refactor: ♻️ refacto and update PHP side

This commit is contained in:
Michel Roux 2024-11-19 19:41:46 +00:00
parent 6e80cae3ec
commit 4e0d69385f
27 changed files with 4427 additions and 667 deletions

3
.gitignore vendored
View File

@ -18,3 +18,6 @@ js/
build/
coverage/
utils/docker-ci
vendor/
.php-cs-fixer.cache

31
.php-cs-fixer.dist.php Normal file
View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
require_once './vendor/autoload.php';
use Nextcloud\CodingStandard\Config;
class MyConfig extends Config
{
public function getRules(): array
{
$rules = parent::getRules();
$rules['@PhpCsFixer'] = true;
$rules['curly_braces_position']['classes_opening_brace'] = 'next_line_unless_newline_at_signature_end';
$rules['phpdoc_to_comment'] = false;
return $rules;
}
}
$config = new MyConfig();
$config
->getFinder()
->notPath('build')
->notPath('l10n')
->notPath('node_modules')
->notPath('src')
->notPath('vendor')
->in(__DIR__);
return $config;

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,74 +20,71 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
return [
'resources' => [
'favorite' => ['url' => '/api/favorites'],
'recent' => ['url' => '/api/recent'],
'export' => ['url' => '/export'],
'station' => ['url' => '/station'],
],
'routes' => [
'resources' => [
'favorite' => ['url' => '/api/favorites'],
'recent' => ['url' => '/api/recent'],
'export' => ['url' => '/export'],
'station' => ['url' => '/station'],
],
'routes' => [
// Web page templates
[
'name' => 'page#index',
'url' => '/',
'verb' => 'GET',
],
[
'name' => 'page#index',
'url' => '/top',
'verb' => 'GET',
'postfix' => 'top',
],
[
'name' => 'page#index',
'url' => '/recent',
'verb' => 'GET',
'postfix' => 'recent',
],
[
'name' => 'page#index',
'url' => '/new',
'verb' => 'GET',
'postfix' => 'new',
],
[
'name' => 'page#index',
'url' => '/favorites',
'verb' => 'GET',
'postfix' => 'favorites',
],
[
'name' => 'page#index',
'url' => '/categories',
'verb' => 'GET',
'postfix' => 'categories',
],
[
'name' => 'page#index',
'url' => '/search',
'verb' => 'GET',
'postfix' => 'search',
],
// Web page templates
[
'name' => 'page#index',
'url' => '/',
'verb' => 'GET'
],
[
'name' => 'page#index',
'url' => '/top',
'verb' => 'GET',
'postfix' => 'top'
],
[
'name' => 'page#index',
'url' => '/recent',
'verb' => 'GET',
'postfix' => 'recent'
],
[
'name' => 'page#index',
'url' => '/new',
'verb' => 'GET',
'postfix' => 'new'
],
[
'name' => 'page#index',
'url' => '/favorites',
'verb' => 'GET',
'postfix' => 'favorites'
],
[
'name' => 'page#index',
'url' => '/categories',
'verb' => 'GET',
'postfix' => 'categories'
],
[
'name' => 'page#index',
'url' => '/search',
'verb' => 'GET',
'postfix' => 'search',
],
// Api
[
'name' => 'favorite_api#preflighted_cors',
'url' => '/api/0.1/{path}',
'verb' => 'OPTIONS',
'requirements' => ['path' => '.+']
],
[
'name' => 'recent_api#preflighted_cors',
'url' => '/api/0.1/{path}',
'verb' => 'OPTIONS',
'requirements' => ['path' => '.+']
]
]
// Api
[
'name' => 'favorite_api#preflighted_cors',
'url' => '/api/0.1/{path}',
'verb' => 'OPTIONS',
'requirements' => ['path' => '.+'],
],
[
'name' => 'recent_api#preflighted_cors',
'url' => '/api/0.1/{path}',
'verb' => 'OPTIONS',
'requirements' => ['path' => '.+'],
],
],
];

View File

@ -2,11 +2,35 @@
"name": "onny/radio",
"description": "Lint config for onny/radio",
"license": "AGPL",
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true
"autoload": {
"psr-4": {
"OCA\\Radio\\": "lib/"
}
},
"scripts": {
"lint": "find . -name \\*.php -not -path './vendor/*' -exec php -l \"{}\" \\;"
"lint": "find . -name \\*.php -not -path './vendor/*' -not -path './vendor-bin/*' -not -path './build/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"psalm": "psalm --threads=1 --no-cache --show-info=true",
"rector": "rector && composer cs:fix"
},
"require": {
"php": "^8.1"
},
"require-dev": {
"nextcloud/ocp": "^30.0.2",
"roave/security-advisories": "dev-latest",
"nextcloud/coding-standard": "^1.3.2",
"nextcloud/rector": "^0.2.1",
"rector/rector": "^1.2.10",
"vimeo/psalm": "^5.26.1",
"doctrine/dbal": "^4.2.1"
},
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true,
"platform": {
"php": "8.1"
}
}
}
}

3567
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,25 +18,23 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Radio\AppInfo;
use OC\Security\CSP\ContentSecurityPolicy;
use OCA\Radio\Dashboard\RadioWidget;
use OCA\Radio\Search\SearchProvider;
use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\IRequest;
use Psr\Container\ContainerInterface;
use OCA\Radio\Dashboard\RadioWidget;
class Application extends App implements IBootstrap {
class Application extends App implements IBootstrap
{
public const APP_ID = 'radio';
public function __construct() {
@ -44,32 +42,11 @@ class Application extends App implements IBootstrap {
}
public function register(IRegistrationContext $context): void {
$context->registerSearchProvider(SearchProvider::class);
$context->registerDashboardWidget(RadioWidget::class);
$context->registerService('request', static function ($c) {
return $c->get(IRequest::class);
});
$this->registerCsp();
$context->registerService('request', static fn (ContainerInterface $c): mixed => $c->get(IRequest::class));
}
public function boot(IBootContext $context): void {
}
/**
* Allow radio-browser hosts in the csp
*
* @throws \OCP\AppFramework\QueryException
*/
public function registerCsp() {
$manager = $this->getContainer()->getServer()->getContentSecurityPolicyManager();
$policy = new ContentSecurityPolicy();
$policy->addAllowedConnectDomain('https://de1.api.radio-browser.info');
$policy->addAllowedImageDomain('*');
$policy->addAllowedMediaDomain('*');
$manager->addDefaultPolicy($policy);
}
public function boot(IBootContext $context): void {}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,24 +20,23 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Controller;
use Closure;
use OCA\Radio\Service\StationNotFound;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCA\Radio\Service\StationNotFound;
trait Errors {
protected function handleNotFound(Closure $callback): DataResponse {
trait Errors
{
protected function handleNotFound(\Closure $callback): DataResponse {
try {
/** @psalm-suppress MixedArgument */
return new DataResponse($callback());
} catch (StationNotFound $e) {
$message = ['message' => $e->getMessage()];
} catch (StationNotFound $stationNotFound) {
$message = ['message' => $stationNotFound->getMessage()];
return new DataResponse($message, Http::STATUS_NOT_FOUND);
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,60 +20,70 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Controller;
use OC;
use OCA\Radio\ExportResponse;
use OCA\Radio\AppInfo\Application;
use OCA\Radio\Service\FavoriteService;
use OCA\Radio\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\Defaults;
use OCP\HintException;
use OCP\IDateTimeFormatter;
use OCP\IRequest;
use SimpleXMLElement;
use DOMDocument;
class ExportController extends Controller {
/** @var FavoriteService */
private $service;
/** @var string */
private $userId;
class ExportController extends Controller
{
use Errors;
public function __construct(IRequest $request,
FavoriteService $service,
$userId) {
public function __construct(
IRequest $request,
private readonly FavoriteService $service,
private readonly UserService $user,
private readonly Defaults $defaults,
private readonly IDateTimeFormatter $dateTime,
) {
parent::__construct(Application::APP_ID, $request);
$this->service = $service;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
public function index() {
$xml = new SimpleXMLElement('<?xml version="1.0"?><playlist></playlist>');
$xml->addAttribute('encoding', 'UTF-8');
$trackList = $xml->addChild('trackList');
foreach($this->service->findAll($this->userId) as $station) {
$track = $trackList->addChild('track');
$track->addChild('location', $station->getUrlresolved());
$track->addChild('title', $station->getName());
$track->addChild('image', $station->getFavicon());
#[NoAdminRequired]
public function index(): DataDownloadResponse {
$user = $this->user->getUser();
if (is_null($user)) {
throw new HintException('User not logged in');
}
$dom = new DOMDocument("1.0");
$xml = new \SimpleXMLElement('<?xml version="1.0"?><playlist></playlist>');
$xml->addAttribute('encoding', 'UTF-8');
$trackList = $xml->addChild('trackList');
if ($trackList instanceof \SimpleXMLElement) {
foreach ($this->service->findAll($user->getUID()) as $station) {
$track = $trackList->addChild('track');
if ($track instanceof \SimpleXMLElement) {
$track->addChild('location', $station->getUrlresolved());
$track->addChild('title', $station->getName());
$track->addChild('image', $station->getFavicon());
}
}
}
$dom = new \DOMDocument('1.0');
$dom->preserveWhiteSpace = false;
$dom->formatOutput = true;
/** @psalm-suppress ArgumentTypeCoercion */
$dom->loadXML($xml->asXML());
$returnstring = $dom->saveXML();
return new ExportResponse($dom->saveXML());
$userName = $user->getDisplayName();
$productName = $this->defaults->getName();
$date = $this->dateTime->formatDate(time());
$export_name = sprintf('%s Radio Favorites (%s) (%s).xspf', $productName, $userName, $date);
return new DataDownloadResponse($returnstring, $export_name, 'application/xspf+xml');
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,83 +20,101 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Controller;
use OCA\Radio\AppInfo\Application;
use OCA\Radio\Db\Station;
use OCA\Radio\Service\FavoriteService;
use OCA\Radio\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
class FavoriteController extends Controller {
/** @var FavoriteService */
private $service;
/** @var string */
private $userId;
class FavoriteController extends Controller
{
use Errors;
public function __construct(IRequest $request,
FavoriteService $service,
$userId) {
public function __construct(
IRequest $request,
private readonly FavoriteService $service,
private readonly UserService $user,
) {
parent::__construct(Application::APP_ID, $request);
$this->service = $service;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function index(): DataResponse {
return new DataResponse($this->service->findAll($this->userId));
return new DataResponse($this->service->findAll($this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function show(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->find($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->find($id, $this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
public function create(string $stationuuid, string $name, string $favicon, string $urlresolved,
string $bitrate, string $country, string $language, string $homepage,
string $codec, string $tags): DataResponse {
return new DataResponse($this->service->create($stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId));
}
#[NoAdminRequired]
public function create(
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return new DataResponse($this->service->create(
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
public function update(int $id, string $stationuuid,
string $name, string $favicon, string $urlresolved,
string $bitrate, string $country, string $language, string $homepage,
string $codec, string $tags): DataResponse {
return $this->handleNotFound(function () use ($id, $stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags) {
return $this->service->update($id, $stationuuid, $name, $favicon,
$urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId);
});
}
#[NoAdminRequired]
public function update(
int $id,
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return $this->handleNotFound(fn (): ?Station => $this->service->update(
$id,
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function destroy(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->delete($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->delete($id, $this->user->getUserUID()));
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,36 +18,42 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Radio\Controller;
use OCP\IRequest;
use OCP\AppFramework\Http\TemplateResponse;
use OCA\Radio\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest;
use OCP\Util;
class PageController extends Controller {
protected $appName;
public function __construct($appName, IRequest $request) {
parent::__construct($appName, $request);
$this->appName = $appName;
class PageController extends Controller
{
public function __construct(IRequest $request) {
parent::__construct(Application::APP_ID, $request);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
public function index() {
Util::addScript($this->appName, 'radio-main');
Util::addStyle($this->appName, 'icons');
#[NoAdminRequired]
#[NoCSRFRequired]
public function index(): TemplateResponse {
Util::addScript(Application::APP_ID, 'radio-main');
Util::addStyle(Application::APP_ID, 'icons');
// Allow radio-browser hosts in the csp
$policy = new ContentSecurityPolicy();
$policy->addAllowedConnectDomain('https://de1.api.radio-browser.info');
$policy->addAllowedImageDomain('*');
$policy->addAllowedMediaDomain('*');
$response = new TemplateResponse(Application::APP_ID, 'main');
$response->setContentSecurityPolicy($policy);
$response = new TemplateResponse($this->appName, 'main');
return $response;
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,82 +20,101 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Controller;
use OCA\Radio\AppInfo\Application;
use OCA\Radio\Db\Station;
use OCA\Radio\Service\RecentService;
use OCA\Radio\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
class RecentController extends Controller {
/** @var RecentService */
private $service;
/** @var string */
private $userId;
class RecentController extends Controller
{
use Errors;
public function __construct(IRequest $request,
RecentService $service,
$userId) {
public function __construct(
IRequest $request,
private readonly RecentService $service,
private readonly UserService $user,
) {
parent::__construct(Application::APP_ID, $request);
$this->service = $service;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function index(): DataResponse {
return new DataResponse($this->service->findAll($this->userId));
return new DataResponse($this->service->findAll($this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function show(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->find($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->find($id, $this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
public function create(string $stationuuid, string $name, string $favicon, string $urlresolved,
string $bitrate, string $country, string $language, string $homepage,
string $codec, string $tags): DataResponse {
return new DataResponse($this->service->create($stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId));
#[NoAdminRequired]
public function create(
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return new DataResponse($this->service->create(
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
public function update(int $id, string $stationuuid, string $name,
string $favicon, string $urlresolved, string $bitrate, string $country,
string $language, string $homepage, string $codec, string $tags): DataResponse {
return $this->handleNotFound(function () use ($id, $stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags) {
return $this->service->update($id, $stationuuid, $name, $favicon,
$urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId);
});
#[NoAdminRequired]
public function update(
int $id,
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return $this->handleNotFound(fn (): ?Station => $this->service->update(
$id,
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function destroy(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->delete($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->delete($id, $this->user->getUserUID()));
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,87 +20,103 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Controller;
use OCA\Radio\AppInfo\Application;
use OCA\Radio\Db\Station;
use OCA\Radio\Service\RadioBrowserApiService;
use OCA\Radio\Service\RecentService;
use OCA\Radio\Service\UserService;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
class StationController extends Controller {
/** @var StationController */
private $service;
/** @var RadioBrowserApiService */
private $radiobrowserapi;
/** @var string */
private $userId;
class StationController extends Controller
{
use Errors;
public function __construct(IRequest $request,
RecentService $service,
RadioBrowserApiService $radiobrowserapi,
$userId) {
public function __construct(
IRequest $request,
private readonly RecentService $service,
private readonly RadioBrowserApiService $radiobrowserapi,
private readonly UserService $user,
) {
parent::__construct(Application::APP_ID, $request);
$this->service = $service;
$this->radiobrowserapi = $radiobrowserapi;
$this->userId = $userId;
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function index(): DataResponse {
return new DataResponse($this->service->findAll($this->userId));
return new DataResponse($this->service->findAll($this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function show(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->find($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->find($id, $this->user->getUserUID()));
}
/**
* @NoAdminRequired
*/
public function create(string $stationuuid, string $name, string $favicon, string $urlresolved,
string $bitrate, string $country, string $language, string $homepage,
string $codec, string $tags): DataResponse {
return new DataResponse($this->service->create($stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId));
#[NoAdminRequired]
public function create(
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return new DataResponse($this->service->create(
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
public function update(int $id, string $stationuuid, string $name,
string $favicon, string $urlresolved, string $bitrate, string $country,
string $language, string $homepage, string $codec, string $tags): DataResponse {
return $this->handleNotFound(function () use ($id, $stationuuid, $name,
$favicon, $urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags) {
return $this->service->update($id, $stationuuid, $name, $favicon,
$urlresolved, $bitrate, $country, $language, $homepage, $codec,
$tags, $this->userId);
});
#[NoAdminRequired]
public function update(
int $id,
string $stationuuid,
string $name,
string $favicon,
string $urlresolved,
string $bitrate,
string $country,
string $language,
string $homepage,
string $codec,
string $tags
): DataResponse {
return $this->handleNotFound(fn (): ?Station => $this->service->update(
$id,
$stationuuid,
$name,
$favicon,
$urlresolved,
$bitrate,
$country,
$language,
$homepage,
$codec,
$tags,
$this->user->getUserUID()
));
}
/**
* @NoAdminRequired
*/
#[NoAdminRequired]
public function destroy(int $id): DataResponse {
return $this->handleNotFound(function () use ($id) {
return $this->service->delete($id, $this->userId);
});
return $this->handleNotFound(fn (): ?Station => $this->service->delete($id, $this->user->getUserUID()));
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,74 +20,45 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Dashboard;
use OCA\Radio\AppInfo\Application;
use OCP\Dashboard\IWidget;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Util;
use OCA\Radio\AppInfo\Application;
class RadioWidget implements IWidget {
/** @var IL10N */
private $l10n;
/** @var IURLGenerator */
private $urlGenerator;
class RadioWidget implements IWidget
{
public function __construct(
IL10N $l10n,
IURLGenerator $urlGenerator
) {
$this->l10n = $l10n;
$this->urlGenerator = $urlGenerator;
}
private readonly IL10N $l10n,
private readonly IURLGenerator $urlGenerator
) {}
/**
* @inheritDoc
*/
public function getId(): string {
return Application::APP_ID;
}
/**
* @inheritDoc
*/
public function getTitle(): string {
return $this->l10n->t('Radio stations');
}
return $this->l10n->t('Radio stations');
}
/**
* @inheritDoc
*/
public function getOrder(): int {
return 10;
}
/**
* @inheritDoc
*/
public function getIconClass(): string {
return 'icon-radio';
}
/**
* @inheritDoc
*/
public function getUrl(): ?string {
return $this->urlGenerator->getAbsoluteURL($this->urlGenerator->linkToRoute('radio.page.index'));
}
/**
* @inheritDoc
*/
public function load(): void {
Util::addScript(Application::APP_ID, 'radio-dashboard');
Util::addStyle(Application::APP_ID, 'dashboard');
Util::addScript(Application::APP_ID, 'radio-dashboard');
Util::addStyle(Application::APP_ID, 'dashboard');
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,49 +20,50 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class FavoriteMapper extends QBMapper {
/**
* @extends QBMapper<Station>
*/
class FavoriteMapper extends QBMapper
{
public function __construct(IDBConnection $db) {
parent::__construct($db, 'favorites', Station::class);
}
/**
* @param int $id
* @param string $userId
* @return Entity|Station
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id, string $userId): Station {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('favorites')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
;
return $this->findEntity($qb);
}
/**
* @param string $userId
* @return array
* @return Station[]
*/
public function findAll(string $userId): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('favorites')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
;
return $this->findEntities($qb);
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,31 +20,30 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Db;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
class RecentMapper extends QBMapper {
/**
* @extends QBMapper<Station>
*/
class RecentMapper extends QBMapper
{
public function __construct(IDBConnection $db) {
parent::__construct($db, 'recent', Station::class);
}
/**
* @param int $id
* @param string $userId
* @return Entity|Station
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws MultipleObjectsReturnedException
* @throws DoesNotExistException
*/
public function find(int $id, string $userId): Station {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->selectDistinct('stationuuid')
->addSelect('name')
@ -57,16 +58,16 @@ class RecentMapper extends QBMapper {
->from('recent')
->orderBy('id', 'DESC')
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
;
return $this->findEntity($qb);
}
/**
* @param string $userId
* @return array
* @return Station[]
*/
public function findAll(string $userId): array {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$qb->selectDistinct('stationuuid')
->addSelect('name')
@ -80,7 +81,9 @@ class RecentMapper extends QBMapper {
->addSelect('tags')
->from('recent')
->orderBy('id', 'DESC')
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)))
;
return $this->findEntities($qb);
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,27 +20,61 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Db;
use JsonSerializable;
use OCP\AppFramework\Db\Entity;
class Station extends Entity implements JsonSerializable {
protected $stationuuid;
protected $name;
protected $favicon;
protected $urlresolved;
protected $bitrate;
protected $country;
protected $language;
protected $homepage;
protected $codec;
protected $tags;
protected $userId;
/**
* @method string getStationuuid()
* @method string getName()
* @method ?string getFavicon()
* @method ?string getUrlresolved()
* @method ?string getBitrate()
* @method ?string getCountry()
* @method ?string getLanguage()
* @method ?string getHomepage()
* @method ?string getCodec()
* @method ?string getTags()
* @method string getUserId()
* @method void setStationuuid(string $stationuuid)
* @method void setName(string $name)
* @method void setFavicon(?string $favicon)
* @method void setUrlresolved(?string $urlresolved)
* @method void setBitrate(?string $bitrate)
* @method void setCountry(?string $country)
* @method void setLanguage(?string $language)
* @method void setHomepage(?string $homepage)
* @method void setCodec(?string $codec)
* @method void setTags(?string $tags)
* @method void setUserId(string $userId)
*
* @psalm-suppress PropertyNotSetInConstructor
*/
class Station extends Entity implements \JsonSerializable
{
protected string $stationuuid;
protected string $name;
protected ?string $favicon = null;
protected ?string $urlresolved = null;
protected ?string $bitrate = null;
protected ?string $country = null;
protected ?string $language = null;
protected ?string $homepage = null;
protected ?string $codec = null;
protected ?string $tags = null;
protected string $userId;
public function jsonSerialize(): array {
return [
@ -52,7 +88,8 @@ class Station extends Entity implements JsonSerializable {
'language' => $this->language,
'homepage' => $this->homepage,
'codec' => $this->codec,
'tags' => $this->tags
'tags' => $this->tags,
'userId' => $this->userId,
];
}
}

View File

@ -1,56 +0,0 @@
<?php
/**
* Radio App
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio;
use OC;
use OC\HintException;
use OCP\AppFramework\Http\Response;
class ExportResponse extends Response {
private $returnstring;
public function __construct($returnstring) {
parent::__construct();
$user = OC::$server->getUserSession()->getUser();
if (is_null($user)) {
throw new HintException('User not logged in');
}
$userName = $user->getDisplayName();
$productName = OC::$server->getThemingDefaults()->getName();
$dateTime = OC::$server->getDateTimeFormatter();
$export_name = '"' . $productName . ' Radio Favorites (' . $userName . ') (' . $dateTime->formatDate(time()) . ').xspf"';
$this->addHeader("Cache-Control", "private");
$this->addHeader("Content-Type", " application/xspf+xml");
$this->addHeader("Content-Length", strlen($returnstring));
$this->addHeader("Content-Disposition", "attachment; filename=" . $export_name);
$this->returnstring = $returnstring;
}
public function render() {
return $this->returnstring;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,27 +18,24 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Radio\Migration;
use Closure;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version000000Date20181013124731 extends SimpleMigrationStep {
class Version000000Date20181013124731 extends SimpleMigrationStep
{
/**
* @param IOutput $output
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
*
* @return null|ISchemaWrapper
*/
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,7 +18,6 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
@ -26,32 +25,22 @@ declare(strict_types=1);
namespace OCA\Radio\Search;
use OCA\Radio\AppInfo\Application;
use OCP\IUser;
use OCP\Http\Client\IClientService;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\Search\IProvider;
use OCP\Search\ISearchQuery;
use OCP\Search\SearchResult;
use OCP\Search\SearchResultEntry;
use Psr\Log\LoggerInterface;
use OCP\Http\Client\IClientService;
use function urlencode;
class SearchProvider implements IProvider {
/** @var IClientService */
private $clientService;
/** @var IURLGenerator */
private $url;
class SearchProvider implements IProvider
{
public function __construct(
IClientService $clientService,
IURLGenerator $url
) {
$this->clientService = $clientService;
$this->url = $url;
}
private readonly IClientService $clientService,
private readonly IURLGenerator $url,
private readonly LoggerInterface $logger,
) {}
public function getId(): string {
return Application::APP_ID;
@ -62,38 +51,43 @@ class SearchProvider implements IProvider {
}
public function getOrder(string $route, array $routeParameters): int {
if (strpos($route, 'files' . '.') === 0) {
if (str_starts_with($route, 'files.')) {
return 25;
} elseif (strpos($route, Application::APP_ID . '.') === 0) {
}
if (str_starts_with($route, Application::APP_ID.'.')) {
return -1;
}
return 4;
}
public function search(IUser $user, ISearchQuery $query): SearchResult {
public function search(IUser $user, ISearchQuery $query): SearchResult {
$term = $query->getTerm();
$url = "https://de1.api.radio-browser.info/json/stations/byname/" . $term . "?limit=20";
$url = 'https://de1.api.radio-browser.info/json/stations/byname/'.$term.'?limit=20';
$client = $this->clientService->newClient();
try {
$response = $client->get($url);
} catch (Exception $e) {
$this->logger->error("Could not search for radio stations: " . $e->getMessage());
throw $e;
} catch (\Exception $exception) {
$this->logger->error('Could not search for radio stations: '.$exception->getMessage());
throw $exception;
}
$body = $response->getBody();
$body = (string) $response->getBody();
/** @var array<array<string, string>> $parsed */
$parsed = json_decode($body, true);
$result = array_map(function (array $result) use ($term) {
return new SearchResultEntry(
$result['favicon'],
$result['name'],
str_replace(",",", ",$result['tags']),
$this->url->linkToRouteAbsolute('radio.page.index') . '#/search/' . $term,
'icon-radio-trans'
);
}, $parsed);
$result = array_map(fn (array $result): SearchResultEntry => new SearchResultEntry(
$result['favicon'],
$result['name'],
str_replace(',', ', ', $result['tags']),
$this->url->linkToRouteAbsolute('radio.page.index').'#/search/'.$term,
'icon-radio-trans'
), $parsed);
return SearchResult::complete(
$this->getName(),

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,56 +20,55 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Service;
use Exception;
use OCA\Radio\Db\FavoriteMapper;
use OCA\Radio\Db\Station;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCA\Radio\Db\Station;
use OCA\Radio\Db\FavoriteMapper;
class FavoriteService {
/** @var FavoriteMapper */
private $mapper;
public function __construct(FavoriteMapper $mapper) {
class FavoriteService
{
public function __construct(
private readonly FavoriteMapper $mapper
) {
$this->mapper = $mapper;
}
/**
* @return Station[]
*/
public function findAll(string $userId): array {
return $this->mapper->findAll($userId);
}
private function handleException(Exception $e): void {
if ($e instanceof DoesNotExistException ||
$e instanceof MultipleObjectsReturnedException) {
throw new StationNotFound($e->getMessage());
} else {
throw $e;
}
}
public function find($id, $userId) {
public function find(int $id, string $userId): ?Station {
try {
return $this->mapper->find($id, $userId);
// in order to be able to plug in different storage backends like files
// for instance it is a good idea to turn storage related exceptions
// into service related exceptions so controllers and service users
// have to deal with only one type of exception
} catch (Exception $e) {
$this->handleException($e);
// for instance it is a good idea to turn storage related exceptions
// into service related exceptions so controllers and service users
// have to deal with only one type of exception
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
public function create($stationuuid, $name, $favicon, $urlresolved,
$bitrate, $country, $language, $homepage, $codec, $tags, $userId) {
public function create(
string $stationuuid,
string $name,
?string $favicon,
?string $urlresolved,
?string $bitrate,
?string $country,
?string $language,
?string $homepage,
?string $codec,
?string $tags,
string $userId
): Station {
$station = new Station();
$station->setStationuuid($stationuuid);
$station->setName($name);
@ -80,11 +81,24 @@ class FavoriteService {
$station->setCodec($codec);
$station->setTags($tags);
$station->setUserId($userId);
return $this->mapper->insert($station);
}
public function update($id, $stationuuid, $name, $favicon, $urlresolved,
$bitrate, $country, $language, $homepage, $codec, $tags, $userId) {
public function update(
int $id,
string $stationuuid,
string $name,
?string $favicon,
?string $urlresolved,
?string $bitrate,
?string $country,
?string $language,
?string $homepage,
?string $codec,
?string $tags,
string $userId
): ?Station {
try {
$station = $this->mapper->find($id, $userId);
$station->setStationuuid($stationuuid);
@ -97,19 +111,30 @@ class FavoriteService {
$station->setHomepage($homepage);
$station->setCodec($codec);
$station->setTags($tags);
return $this->mapper->update($station);
} catch (Exception $e) {
$this->handleException($e);
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
public function delete($id, $userId) {
public function delete(int $id, string $userId): ?Station {
try {
$station = $this->mapper->find($id, $userId);
$this->mapper->delete($station);
return $station;
} catch (Exception $e) {
$this->handleException($e);
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
private function handleException(\Throwable $e): void {
if ($e instanceof DoesNotExistException
|| $e instanceof MultipleObjectsReturnedException) {
throw new StationNotFound($e->getMessage());
}
throw $e;
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,119 +18,116 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
declare(strict_types=1);
namespace OCA\Radio\Service;
use OCP\IURLGenerator;
use OCP\Http\Client\IClientService;
use Psr\Log\LoggerInterface;
use function urlencode;
class RadioBrowserApiService {
/** @var IClientService */
private $clientService;
/** @var IURLGenerator */
private $url;
public $baseUrl = "https://api.fyyd.de/0.2";
class RadioBrowserApiService
{
public string $baseUrl = 'https://api.fyyd.de/0.2';
public function __construct(
IClientService $clientService,
IURLGenerator $url
) {
$this->clientService = $clientService;
$this->url = $url;
}
private readonly IClientService $clientService,
private readonly LoggerInterface $logger,
) {}
public function queryEpisodes(int $podcast_id, int $count = 20, int $page = 0) {
$url = $this->baseUrl . "/podcast/episodes";
public function queryEpisodes(int $podcast_id, int $count = 20, int $page = 0): mixed {
$url = $this->baseUrl.'/podcast/episodes';
$options = [];
$options['query'] = [
'podcast_id' => $podcast_id,
'count' => $count,
'page' => $page
'page' => $page,
];
$client = $this->clientService->newClient();
try {
$response = $client->get($url, $options);
} catch (Exception $e) {
$this->logger->error("Could not search for podcasts: " . $e->getMessage());
throw $e;
} catch (\Exception $exception) {
$this->logger->error('Could not search for podcasts: '.$exception->getMessage());
throw $exception;
}
$body = $response->getBody();
$parsed = json_decode($body, true);
$body = (string) $response->getBody();
return $parsed;
return json_decode($body, true);
}
public function queryEpisode(int $episode_id) {
$url = $this->baseUrl . "/episode";
public function queryEpisode(int $episode_id): mixed {
$url = $this->baseUrl.'/episode';
$options = [];
$options['query'] = [
'episode_id' => $episode_id,
];
$client = $this->clientService->newClient();
try {
$response = $client->get($url, $options);
} catch (Exception $e) {
$this->logger->error("Could not search for podcasts: " . $e->getMessage());
throw $e;
}
$body = $response->getBody();
$parsed = json_decode($body, true);
} catch (\Exception $exception) {
$this->logger->error('Could not search for podcasts: '.$exception->getMessage());
return $parsed;
throw $exception;
}
$body = (string) $response->getBody();
return json_decode($body, true);
}
public function queryPodcast(int $podcast_id) {
$url = $this->baseUrl . "/podcast";
public function queryPodcast(int $podcast_id): mixed {
$url = $this->baseUrl.'/podcast';
$options = [];
$options['query'] = [
'podcast_id' => $podcast_id,
];
$client = $this->clientService->newClient();
try {
$response = $client->get($url, $options);
} catch (Exception $e) {
$this->logger->error("Could not search for podcasts: " . $e->getMessage());
throw $e;
}
$body = $response->getBody();
$parsed = json_decode($body, true);
} catch (\Exception $exception) {
$this->logger->error('Could not search for podcasts: '.$exception->getMessage());
return $parsed;
throw $exception;
}
$body = (string) $response->getBody();
return json_decode($body, true);
}
public function queryCategory(string $category, int $count = 20,
int $page = 0) {
public function queryCategory(
string $category,
int $count = 20,
int $page = 0
): mixed {
$options = [];
if ($category === 'hot') {
$url = $this->baseUrl . "/feature/podcast/hot";
if ('hot' === $category) {
$url = $this->baseUrl.'/feature/podcast/hot';
$options['query'] = [
'count' => $count,
'page' => $page,
];
} else if ($category === 'latest') {
$url = $this->baseUrl . "/podcast/latest";
} elseif ('latest' === $category) {
$url = $this->baseUrl.'/podcast/latest';
$options['query'] = [
'count' => $count,
'page' => $page,
];
} else {
$url = $this->baseUrl . "/category";
$url = $this->baseUrl.'/category';
$options['query'] = [
'count' => $count,
'page' => $page,
@ -139,15 +136,17 @@ class RadioBrowserApiService {
}
$client = $this->clientService->newClient();
try {
$response = $client->get($url, $options);
} catch (Exception $e) {
$this->logger->error("Could not search for podcasts: " . $e->getMessage());
throw $e;
}
$body = $response->getBody();
$parsed = json_decode($body, true);
} catch (\Exception $exception) {
$this->logger->error('Could not search for podcasts: '.$exception->getMessage());
return $parsed;
throw $exception;
}
$body = (string) $response->getBody();
return json_decode($body, true);
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,56 +20,55 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Service;
use Exception;
use OCA\Radio\Db\RecentMapper;
use OCA\Radio\Db\Station;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCA\Radio\Db\Station;
use OCA\Radio\Db\RecentMapper;
class RecentService {
/** @var RecentMapper */
private $mapper;
public function __construct(RecentMapper $mapper) {
class RecentService
{
public function __construct(
private readonly RecentMapper $mapper
) {
$this->mapper = $mapper;
}
/**
* @return Station[]
*/
public function findAll(string $userId): array {
return $this->mapper->findAll($userId);
}
private function handleException(Exception $e): void {
if ($e instanceof DoesNotExistException ||
$e instanceof MultipleObjectsReturnedException) {
throw new StationNotFound($e->getMessage());
} else {
throw $e;
}
}
public function find($id, $userId) {
public function find(int $id, string $userId): ?Station {
try {
return $this->mapper->find($id, $userId);
// in order to be able to plug in different storage backends like files
// for instance it is a good idea to turn storage related exceptions
// into service related exceptions so controllers and service users
// have to deal with only one type of exception
} catch (Exception $e) {
$this->handleException($e);
// for instance it is a good idea to turn storage related exceptions
// into service related exceptions so controllers and service users
// have to deal with only one type of exception
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
public function create($stationuuid, $name, $favicon, $urlresolved,
$bitrate, $country, $language, $homepage, $codec, $tags, $userId) {
public function create(
string $stationuuid,
string $name,
?string $favicon,
?string $urlresolved,
?string $bitrate,
?string $country,
?string $language,
?string $homepage,
?string $codec,
?string $tags,
string $userId
): Station {
$station = new Station();
$station->setStationuuid($stationuuid);
$station->setName($name);
@ -80,11 +81,24 @@ class RecentService {
$station->setCodec($codec);
$station->setTags($tags);
$station->setUserId($userId);
return $this->mapper->insert($station);
}
public function update($id, $stationuuid, $name, $favicon, $urlresolved,
$bitrate, $country, $language, $homepage, $codec, $tags, $userId) {
public function update(
int $id,
string $stationuuid,
string $name,
?string $favicon,
?string $urlresolved,
?string $bitrate,
?string $country,
?string $language,
?string $homepage,
?string $codec,
?string $tags,
string $userId
): ?Station {
try {
$station = $this->mapper->find($id, $userId);
$station->setStationuuid($stationuuid);
@ -97,19 +111,33 @@ class RecentService {
$station->setHomepage($homepage);
$station->setCodec($codec);
$station->setTags($tags);
return $this->mapper->update($station);
} catch (Exception $e) {
$this->handleException($e);
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
public function delete($id, $userId) {
public function delete(int $id, string $userId): ?Station {
try {
$station = $this->mapper->find($id, $userId);
$this->mapper->delete($station);
return $station;
} catch (Exception $e) {
$this->handleException($e);
} catch (\Exception $exception) {
return $this->handleException($exception);
}
}
/**
* @throws \Exception
*/
private function handleException(\Throwable $e): void {
if ($e instanceof DoesNotExistException
|| $e instanceof MultipleObjectsReturnedException) {
throw new StationNotFound($e->getMessage());
}
throw $e;
}
}

View File

@ -1,7 +1,9 @@
<?php
declare(strict_types=1);
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,10 +20,8 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Radio\Service;
class StationNotFound extends \Exception {
}
class StationNotFound extends \Exception {}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace OCA\Radio\Service;
use OCP\IUser;
use OCP\IUserSession;
class UserService
{
public function __construct(
private readonly IUserSession $userSession
) {}
public function getUserUID(): string {
$user = $this->getUser();
return $user instanceof IUser ? $user->getUID() : '';
}
public function getUser(): ?IUser {
return $this->userSession->getUser();
}
}

21
psalm.xml Normal file
View File

@ -0,0 +1,21 @@
<?xml version="1.0"?>
<psalm
errorLevel="1"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="false"
phpVersion="8.1"
>
<projectFiles>
<directory name="lib" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<extraFiles>
<directory name="vendor"/>
</extraFiles>
</psalm>

34
rector.php Normal file
View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
use Nextcloud\Rector\Set\NextcloudSets;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__.'/appinfo',
__DIR__.'/lib',
])
->withPhpSets(php81: true)
->withSets([
NextcloudSets::NEXTCLOUD_27,
])
->withPreparedSets(
deadCode: true,
codeQuality: true,
codingStyle: true,
typeDeclarations: true,
privatization: true,
instanceOf: true,
earlyReturn: true,
strictBooleans: true,
rectorPreset: true,
phpunitCodeQuality: true,
doctrineCodeQuality: true,
symfonyCodeQuality: true,
symfonyConfigs: true,
twig: true,
phpunit: true,
)
;

View File

@ -1,7 +1,7 @@
<?php
/**
* Radio App
* Radio App.
*
* @author Jonas Heinrich
* @copyright 2021 Jonas Heinrich <onny@project-insanity.org>
@ -18,7 +18,5 @@
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
echo "<div id='vue-content'></div>";