Massive API & WebUI changes.

This commit is contained in:
abdulmohsen
2024-05-04 19:09:19 +03:00
parent af41ac6171
commit 9d966b9b40
50 changed files with 1117 additions and 662 deletions

View File

@@ -2,47 +2,43 @@
declare(strict_types=1);
namespace App\API\Plex;
namespace App\API\Backend;
use App\Libs\Attributes\Route\Post;
use App\Libs\DataUtil;
use App\Libs\Exceptions\RuntimeException;
use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
use Throwable;
final class GenerateAccessToken
final class AccessToken
{
use APITraits;
public const string URL = '%{api.prefix}/plex/accesstoken';
public function __construct(private iHttp $http)
public function __construct(private readonly iHttp $http)
{
}
#[Post(self::URL . '/{id:backend}[/]', name: 'plex.accesstoken')]
public function gAccesstoken(iRequest $request, array $args = []): iResponse
#[Post(Index::URL . '/{name:backend}/accesstoken[/]', name: 'backend.accesstoken')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
$backend = ag($args, 'id');
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$data = DataUtil::fromArray($request->getParsedBody());
if (null === ($uuid = $data->get('uuid'))) {
return api_error('No User (uuid) was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient($backend);
} catch (RuntimeException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
if (null === ($id = $data->get('id'))) {
return api_error('No id was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient(name: $name);
$token = $client->getUserToken(
userId: $uuid,
userId: $id,
username: $data->get('username', $client->getContext()->backendName . '_user'),
);
@@ -59,7 +55,9 @@ final class GenerateAccessToken
}
return api_response(HTTP_STATUS::HTTP_OK, $arr);
} catch (\Throwable $e) {
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
}

View File

@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace App\API\Backend;
use App\Backends\Plex\PlexClient;
use App\Libs\Attributes\Route\Get;
use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
use Throwable;
final class Discover
{
use APITraits;
public function __construct(private readonly iHttp $http)
{
}
#[Get(Index::URL . '/{name:backend}/discover[/]', name: 'backend.discover')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient(name: $name);
if (PlexClient::CLIENT_NAME !== $client->getType()) {
return api_error('Discover is only available for Plex backends.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
assert($client instanceof PlexClient);
$list = $client::discover($this->http, $client->getContext()->backendToken);
return api_response(HTTP_STATUS::HTTP_OK, ag($list, 'list', []));
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -27,11 +27,11 @@ final class Ignore
$this->file = new ConfigFile(Config::get('path') . '/config/ignore.yaml', type: 'yaml', autoCreate: true);
}
#[Get(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.backend.ignoredIds')]
#[Get(Index::URL . '/{name:backend}/ignore[/]', name: 'backend.ignoredIds')]
public function ignoredIds(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = [];
@@ -61,22 +61,23 @@ final class Ignore
'created' => makeDate($date),
];
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'ignore' => $list,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
return api_response(HTTP_STATUS::HTTP_OK, $list);
}
#[Delete(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.backend.ignoredIds.delete')]
#[Delete(Index::URL . '/{name:backend}/ignore[/]', name: 'backend.ignoredIds.delete')]
public function deleteRule(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$data = $this->getBackends(name: $name);
if (empty($data)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$params = DataUtil::fromRequest($request);
if (null === ($rule = $params->get('rule'))) {
@@ -98,11 +99,11 @@ final class Ignore
return api_response(HTTP_STATUS::HTTP_OK);
}
#[Post(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.backend.ignoredIds.add')]
#[Post(Index::URL . '/{name:backend}/ignore[/]', name: 'backend.ignoredIds.add')]
public function addRule(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$data = $this->getBackends(name: $name);

View File

@@ -16,11 +16,11 @@ final class Index
public const string URL = '%{api.prefix}/backend';
#[Get(self::URL . '/{name:backend}[/]', name: 'backends.view')]
#[Get(self::URL . '/{name:backend}[/]', name: 'backend.view')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$data = $this->getBackends(name: $name);
@@ -29,17 +29,8 @@ final class Index
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$data = array_pop($data);
$response = [
...$data,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, ['backend' => $response]);
return api_response(HTTP_STATUS::HTTP_OK, $data);
}
}

View File

@@ -18,11 +18,11 @@ final class Info
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/info[/]', name: 'backends.backend.info')]
public function backendsView(iRequest $request, array $args = []): iResponse
#[Get(Index::URL . '/{name:backend}/info[/]', name: 'backend.info')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
@@ -40,20 +40,9 @@ final class Info
try {
$data = $client->getInfo($opts);
return api_response(HTTP_STATUS::HTTP_OK, $data);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'data' => $data,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -2,32 +2,51 @@
declare(strict_types=1);
namespace App\API\Backend\Library;
namespace App\API\Backend;
use App\API\Backend\Index as BackendsIndex;
use App\Libs\Attributes\Route\Get;
use App\Libs\Attributes\Route\Route;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\DataUtil;
use App\Libs\Exceptions\RuntimeException;
use App\Libs\HTTP_STATUS;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Throwable;
final class Ignore
final class Library
{
use APITraits;
#[Route(['POST', 'DELETE'], BackendsIndex::URL . '/{name:backend}/library[/]', name: 'backends.library.ignore')]
public function _invoke(iRequest $request, array $args = []): iResponse
#[Get(BackendsIndex::URL . '/{name:backend}/library[/]', name: 'backend.library')]
public function listLibraries(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (null === ($id = DataUtil::fromRequest($request, true)->get('id', null))) {
return api_error('No library id was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
try {
$client = $this->getClient(name: $name);
return api_response(HTTP_STATUS::HTTP_OK, $client->listLibraries());
} catch (RuntimeException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
}
#[Route(['POST', 'DELETE'], BackendsIndex::URL . '/{name:backend}/library/{id}[/]', name: 'backend.library.ignore')]
public function ignoreLibrary(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (null === ($id = ag($args, 'id'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$remove = 'DELETE' === $request->getMethod();
@@ -75,13 +94,6 @@ final class Ignore
$config->set("{$name}.options." . Options::IGNORE, implode(',', array_values($ignoreIds)))->persist();
return api_response(HTTP_STATUS::HTTP_OK, [
'type' => $config->get("{$name}.type"),
'libraries' => $libraries,
'links' => [
'self' => (string)parseConfigValue(BackendsIndex::URL . "/{$name}/library"),
'backend' => (string)parseConfigValue(BackendsIndex::URL . "/{$name}"),
],
]);
return api_response(HTTP_STATUS::HTTP_OK, $libraries);
}
}

View File

@@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
namespace App\API\Backend\Library;
use App\API\Backend\Index as BackendsIndex;
use App\Libs\Attributes\Route\Get;
use App\Libs\Config;
use App\Libs\Exceptions\RuntimeException;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class Index
{
use APITraits;
#[Get(BackendsIndex::URL . '/{name:backend}/library[/]', name: 'backends.library.list')]
public function listLibraries(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient(name: $name);
} catch (RuntimeException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
}
$response = [
'type' => ag(array_flip(Config::get('supported')), $client::class),
'libraries' => $client->listLibraries(),
'links' => [
'self' => (string)$request->getUri()->withHost('')->withPort(0)->withScheme(''),
'backend' => (string)parseConfigValue(BackendsIndex::URL . "/{$name}"),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\API\Backend\Library;
namespace App\API\Backend;
use App\API\Backend\Index as BackendsIndex;
use App\API\Backend\Index as backendIndex;
use App\Commands\Backend\Library\MismatchCommand;
use App\Libs\Attributes\Route\Get;
use App\Libs\DataUtil;
@@ -19,11 +19,11 @@ final class Mismatched
{
use APITraits;
#[Get(BackendsIndex::URL . '/{name:backend}/mismatched[/[{id}[/]]]', name: 'backends.library.mismatched')]
public function listLibraries(iRequest $request, array $args = []): iResponse
#[Get(backendIndex::URL . '/{name:backend}/mismatched[/[{id}[/]]]', name: 'backend.mismatched')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$params = DataUtil::fromArray($request->getQueryParams());
@@ -74,15 +74,6 @@ final class Mismatched
$list[] = $processed;
}
}
$response = [
'items' => $list,
'links' => [
'self' => (string)$request->getUri()->withHost('')->withPort(0)->withScheme(''),
'backend' => (string)parseConfigValue(BackendsIndex::URL . "/{$name}"),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
return api_response(HTTP_STATUS::HTTP_OK, $list);
}
}

171
src/API/Backend/Option.php Normal file
View File

@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);
namespace App\API\Backend;
use App\Libs\Attributes\Route\Delete;
use App\Libs\Attributes\Route\Get;
use App\Libs\Attributes\Route\Patch;
use App\Libs\Attributes\Route\Post;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\DataUtil;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class Option
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option')]
public function viewOption(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (null === ($option = ag($args, 'option'))) {
return api_error('Invalid value for option path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
if (false === $list->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$key = $name . '.options.' . $option;
if (false === $list->has($key)) {
return api_error(r("Option '{option}' not found in backend '{name}'.", [
'option' => $option,
'name' => $name
]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$value = $list->get($key);
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $option,
'value' => $value,
'type' => get_debug_type($value),
]);
}
#[Post(Index::URL . '/{name:backend}/option[/]', name: 'backend.option.add')]
public function addOption(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
if (false === $list->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$data = DataUtil::fromRequest($request);
if (null === ($option = $data->get('key'))) {
return api_error('Invalid value for key.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$spec = require __DIR__ . '/../../../config/backend.spec.php';
$found = false;
foreach ($spec as $supportedKey => $_) {
if (str_ends_with($supportedKey, 'options.' . $option)) {
$found = true;
break;
}
}
if (false === $found) {
return api_error(r("Option '{key}' is not supported.", ['key' => $option]), HTTP_STATUS::HTTP_BAD_REQUEST);
}
$value = $data->get('value');
$list->set($name . '.options.' . $option, $value)->persist();
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $option,
'value' => $value,
'type' => get_debug_type($value),
]);
}
#[Patch(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option.update')]
public function updateOption(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (null === ($option = ag($args, 'option'))) {
return api_error('Invalid value for option parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
if (false === $list->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$key = $name . '.options.' . $option;
if (false === $list->has($key)) {
return api_error(r("Option '{option}' not found in backend '{name}'.", [
'option' => $option,
'name' => $name
]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$data = DataUtil::fromRequest($request);
if (null === ($value = $data->get('value'))) {
return api_error(r("No value was provided for '{key}'.", [
'key' => $key,
]), HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list->set($key, $value)->persist();
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $option,
'value' => $value,
'type' => get_debug_type($value),
]);
}
#[Delete(Index::URL . '/{name:backend}/option/{option}[/]', name: 'backend.option.delete')]
public function deleteOption(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (null === ($option = ag($args, 'option'))) {
return api_error('Invalid value for option option parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
if (false === $list->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$key = $name . '.options.' . $option;
$value = $list->get($key);
$list->delete($key)->persist();
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $option,
'value' => $value,
'type' => get_debug_type($value),
]);
}
}

View File

@@ -1,71 +0,0 @@
<?php
declare(strict_types=1);
namespace App\API\Backend;
use App\Libs\Attributes\Route\Patch;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use JsonException;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class PartialUpdate
{
use APITraits;
#[Patch(Index::URL . '/{name:backend}[/]', name: 'backends.view')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
if (false === $list->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
try {
$data = json_decode((string)$request->getBody(), true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
return api_error(r('Invalid JSON data. {error}', ['error' => $e->getMessage()]),
HTTP_STATUS::HTTP_BAD_REQUEST);
}
foreach ($data as $update) {
if (!ag_exists($update, 'key')) {
return api_error('No key to update was present.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$list->set($name . '.' . ag($update, 'key'), ag($update, 'value'));
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$list->persist();
$backend = $this->getBackends(name: $name);
if (empty($backend)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$backend = array_pop($backend);
return api_response(HTTP_STATUS::HTTP_OK, [
'backend' => array_filter(
$backend,
fn($key) => false === in_array($key, ['options', 'webhook'], true),
ARRAY_FILTER_USE_KEY
),
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
]);
}
}

View File

@@ -17,8 +17,8 @@ final class Search
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/search[/[{id}[/]]]', name: 'backends.backend.search.id')]
public function searchById(iRequest $request, array $args = []): iResponse
#[Get(Index::URL . '/{name:backend}/search[/[{id}[/]]]', name: 'backend.search')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
@@ -55,14 +55,8 @@ final class Search
]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'results' => $id ? [$data] : $data,
'links' => [
'self' => (string)$apiUrl,
'backend' => (string)$apiUrl->withPath(parseConfigValue(Index::URL . '/' . $name)),
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
'options' => [
'raw' => (bool)$params->get('raw', false),
],

View File

@@ -18,11 +18,11 @@ final class Sessions
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/sessions[/]', name: 'backends.backend.sessions')]
public function backendsView(iRequest $request, array $args = []): iResponse
#[Get(Index::URL . '/{name:backend}/sessions[/]', name: 'backend.sessions')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
@@ -40,20 +40,9 @@ final class Sessions
try {
$sessions = $client->getSessions($opts);
return api_response(HTTP_STATUS::HTTP_OK, ag($sessions, 'sessions', []));
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'sessions' => ag($sessions, 'sessions', []),
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -2,9 +2,9 @@
declare(strict_types=1);
namespace App\API\Backend\Library;
namespace App\API\Backend;
use App\API\Backend\Index as BackendsIndex;
use App\API\Backend\Index as backendIndex;
use App\Libs\Attributes\Route\Get;
use App\Libs\DataUtil;
use App\Libs\Exceptions\RuntimeException;
@@ -18,11 +18,11 @@ final class Unmatched
{
use APITraits;
#[Get(BackendsIndex::URL . '/{name:backend}/unmatched[/[{id}[/]]]', name: 'backends.library.unmatched')]
public function listLibraries(iRequest $request, array $args = []): iResponse
#[Get(backendIndex::URL . '/{name:backend}/unmatched[/[{id}[/]]]', name: 'backend.unmatched')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('No backend was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$params = DataUtil::fromArray($request->getQueryParams());
@@ -63,14 +63,6 @@ final class Unmatched
}
}
$response = [
'items' => $list,
'links' => [
'self' => (string)$request->getUri()->withHost('')->withPort(0)->withScheme(''),
'backend' => (string)parseConfigValue(BackendsIndex::URL . "/{$name}"),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
return api_response(HTTP_STATUS::HTTP_OK, $list);
}
}

144
src/API/Backend/Update.php Normal file
View File

@@ -0,0 +1,144 @@
<?php
declare(strict_types=1);
namespace App\API\Backend;
use App\Backends\Common\ClientInterface as iClient;
use App\Libs\Attributes\Route\Patch;
use App\Libs\Attributes\Route\Put;
use App\Libs\Config;
use App\Libs\ConfigFile;
use App\Libs\DataUtil;
use App\Libs\HTTP_STATUS;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use JsonException;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class Update
{
use APITraits;
private ConfigFile $backendFile;
public function __construct()
{
$this->backendFile = ConfigFile::open(Config::get('backends_file'), 'yaml', autoCreate: true);
}
#[Put(Index::URL . '/{name:backend}[/]', name: 'backend.update')]
public function update(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === $this->backendFile->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$this->backendFile->set(
$name,
$this->fromRequest($this->backendFile->get($name), $request, $this->getClient($name))
)->persist();
$backend = $this->getBackends(name: $name);
if (empty($backend)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$backend = array_pop($backend);
return api_response(HTTP_STATUS::HTTP_OK, $backend);
}
#[Patch(Index::URL . '/{name:backend}[/]', name: 'backend.patch')]
public function patchUpdate(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === $this->backendFile->has($name)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
try {
$data = json_decode((string)$request->getBody(), true, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
return api_error(r('Invalid JSON data. {error}', ['error' => $e->getMessage()]),
HTTP_STATUS::HTTP_BAD_REQUEST);
}
$spec = require __DIR__ . '/../../../config/backend.spec.php';
foreach ($data as $update) {
$key = ag($update, 'key');
$value = ag($update, 'value');
if (null === $key) {
return api_error('No key to update was present.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === in_array($key, $spec, true)) {
return api_error(r('Invalid key to update: {key}', ['key' => $key]), HTTP_STATUS::HTTP_BAD_REQUEST);
}
$this->backendFile->set("{$name}.{$key}", $value);
}
$this->backendFile->persist();
$backend = $this->getBackends(name: $name);
if (empty($backend)) {
return api_error(r("Backend '{name}' not found.", ['name' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
}
$backend = array_pop($backend);
return api_response(HTTP_STATUS::HTTP_OK, $backend);
}
private function fromRequest(array $config, iRequest $request, iClient $client): array
{
$data = DataUtil::fromArray($request->getParsedBody());
$newData = [
'url' => $data->get('url'),
'token' => $data->get('token'),
'user' => $data->get('user'),
'uuid' => $data->get('uuid'),
'export' => [
'enabled' => (bool)$data->get('export.enabled', false),
],
'import' => [
'enabled' => (bool)$data->get('import.enabled', false),
],
'webhook' => [
'match' => [
'user' => (bool)$data->get('webhook.match.user'),
'uuid' => (bool)$data->get('webhook.match.uuid'),
],
],
];
$optionals = [
Options::DUMP_PAYLOAD => 'bool',
Options::LIBRARY_SEGMENT => 'int',
Options::IGNORE => 'string',
];
foreach ($optionals as $key => $type) {
if (null !== ($value = $data->get('options.' . $key))) {
settype($value, $type);
$newData = ag_set($newData, "options.{$key}", $value);
}
}
return deepArrayMerge([$config, $client->fromRequest($newData, $request)]);
}
}

View File

@@ -18,19 +18,13 @@ final class Users
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/users[/]', name: 'backends.backend.users')]
public function backendsView(iRequest $request, array $args = []): iResponse
#[Get(Index::URL . '/{name:backend}/users[/]', name: 'backend.users')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient(name: $name);
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
}
$opts = [];
$params = DataUtil::fromRequest($request, true);
@@ -43,21 +37,11 @@ final class Users
}
try {
$users = $client->getUsersList($opts);
return api_response(HTTP_STATUS::HTTP_OK, $this->getClient(name: $name)->getUsersList($opts));
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'users' => $users,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -5,10 +5,8 @@ declare(strict_types=1);
namespace App\API\Backend;
use App\Libs\Attributes\Route\Get;
use App\Libs\DataUtil;
use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\HTTP_STATUS;
use App\Libs\Options;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
@@ -18,42 +16,19 @@ final class Version
{
use APITraits;
#[Get(Index::URL . '/{name:backend}/version[/]', name: 'backends.backend.info')]
public function backendsView(iRequest $request, array $args = []): iResponse
#[Get(Index::URL . '/{name:backend}/version[/]', name: 'backend.version')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getClient(name: $name);
return api_response(HTTP_STATUS::HTTP_OK, ['version' => $this->getClient(name: $name)->getVersion()]);
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
}
$opts = [];
$params = DataUtil::fromRequest($request, true);
if (true === (bool)$params->get('raw', false)) {
$opts[Options::RAW_RESPONSE] = true;
}
try {
$version = $client->getVersion($opts);
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$response = [
'version' => $version,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, $response);
}
}

View File

@@ -27,20 +27,20 @@ final class Webhooks
{
use APITraits;
private iLogger $accessLog;
private iLogger $logfile;
public function __construct(private iCache $cache)
{
$this->accessLog = new Logger(name: 'http', processors: [new LogMessageProcessor()]);
$this->logfile = new Logger(name: 'webhook', processors: [new LogMessageProcessor()]);
$level = Config::get('webhook.debug') ? Level::Debug : Level::Info;
if (null !== ($logfile = Config::get('webhook.logfile'))) {
$this->accessLog = $this->accessLog->pushHandler(new StreamHandler($logfile, $level, true));
$this->logfile = $this->logfile->pushHandler(new StreamHandler($logfile, $level, true));
}
if (true === inContainer()) {
$this->accessLog->pushHandler(new StreamHandler('php://stderr', $level, true));
$this->logfile->pushHandler(new StreamHandler('php://stderr', $level, true));
}
}
@@ -53,13 +53,27 @@ final class Webhooks
* @return iResponse The response object.
* @throws InvalidArgumentException if cache key is invalid.
*/
#[Route(['POST', 'PUT'], Index::URL . '/{name:backend}/webhook[/]', name: 'webhooks.receive')]
#[Route(['POST', 'PUT'], Index::URL . '/{name:backend}/webhook[/]', name: 'backend.webhook')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($name = ag($args, 'name'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
return $this->process($name, $request)->withHeader('X-Log-Response', '0');
}
/**
* Process the incoming webhook request.
*
* @param string $name The backend name.
* @param iRequest $request The incoming request object.
*
* @return iResponse The response object.
* @throws InvalidArgumentException if cache key is invalid.
*/
private function process(string $name, iRequest $request): iResponse
{
try {
$backend = $this->getBackends(name: $name);
if (empty($backend)) {
@@ -84,7 +98,7 @@ final class Webhooks
if (null === ($requestUser = ag($attr, 'user.id'))) {
$message = "Request payload didn't contain a user id. Backend requires a user check.";
$this->write($request, Level::Info, $message);
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === hash_equals((string)$userId, (string)$requestUser)) {
@@ -93,7 +107,7 @@ final class Webhooks
'config_user' => $userId,
]);
$this->write($request, Level::Info, $message);
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST);
}
}
@@ -101,7 +115,7 @@ final class Webhooks
if (null === ($requestBackendId = ag($attr, 'backend.id'))) {
$message = "Request payload didn't contain the backend unique id.";
$this->write($request, Level::Info, $message);
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === hash_equals((string)$uuid, (string)$requestBackendId)) {
@@ -110,7 +124,7 @@ final class Webhooks
'config_uid' => $uuid,
]);
$this->write($request, Level::Info, $message);
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST);
}
}
@@ -128,7 +142,7 @@ final class Webhooks
'backend' => $client->getName(),
]), forceContext: true);
return $response->withHeader('X-Log-Response', '0');
return $response;
}
$entity = $client->parseWebhook($request);
@@ -151,7 +165,7 @@ final class Webhooks
]
);
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED)->withHeader('X-Log-Response', '0');
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED);
}
if ((0 === (int)$entity->episode || null === $entity->season) && $entity->isEpisode()) {
@@ -170,7 +184,7 @@ final class Webhooks
]
);
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED)->withHeader('X-Log-Response', '0');
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED);
}
$items = $this->cache->get('requests', []);
@@ -211,7 +225,7 @@ final class Webhooks
]
);
return api_response(HTTP_STATUS::HTTP_OK)->withHeader('X-Log-Response', '0');
return api_response(HTTP_STATUS::HTTP_OK);
}
/**
@@ -257,9 +271,9 @@ final class Webhooks
}
if (true === (Config::get('logs.context') || $forceContext)) {
$this->accessLog->log($level, $message, $context);
$this->logfile->log($level, $message, $context);
} else {
$this->accessLog->log($level, r($message, $context));
$this->logfile->log($level, r($message, $context));
}
}
}

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace App\API\Backends;
use App\Backends\Common\Cache as BackendCache;
use App\Backends\Common\ClientInterface;
use App\Backends\Common\ClientInterface as iClient;
use App\Backends\Common\Context;
use App\Libs\Attributes\Route\Post;
use App\Libs\Config;
@@ -80,7 +80,7 @@ final class Add
}
$instance = Container::getNew($class);
assert($instance instanceof ClientInterface, new RuntimeException('Invalid client class.'));
assert($instance instanceof iClient, new RuntimeException('Invalid client class.'));
try {
$config = DataUtil::fromArray($this->fromRequest($type, $request, $instance));
@@ -118,15 +118,10 @@ final class Add
$data = $this->getBackends(name: $name);
$data = array_pop($data);
return api_response(HTTP_STATUS::HTTP_CREATED, [
...$data,
'links' => [
'self' => parseConfigValue(Index::URL) . '/' . $name,
],
]);
return api_response(HTTP_STATUS::HTTP_CREATED, $data);
}
private function fromRequest(string $type, iRequest $request, ClientInterface|null $client = null): array
private function fromRequest(string $type, iRequest $request, iClient $client): array
{
$data = DataUtil::fromArray($request->getParsedBody());
@@ -162,16 +157,11 @@ final class Add
foreach ($optionals as $key => $type) {
if (null !== ($value = $data->get('options.' . $key))) {
$val = $data->get($value, $type);
settype($val, $type);
$config = ag_set($config, "options.{$key}", $val);
settype($value, $type);
$config = ag_set($config, "options.{$key}", $value);
}
}
if (null !== $client) {
$config = $client->fromRequest($config, $request);
}
return $config;
return $client->fromRequest($config, $request);
}
}

View File

@@ -23,36 +23,19 @@ final class Index
'options.' . Options::ADMIN_TOKEN
];
#[Get(self::URL . '[/]', name: 'backends.index')]
#[Get(self::URL . '[/]', name: 'backends')]
public function __invoke(iRequest $request): iResponse
{
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$urlPath = $request->getUri()->getPath();
$response = [
'backends' => [],
'links' => [
'self' => (string)$apiUrl,
],
];
$list = [];
foreach ($this->getBackends() as $backend) {
$backend = array_filter(
$list[] = array_filter(
$backend,
fn($key) => false === in_array($key, ['options', 'webhook'], true),
ARRAY_FILTER_USE_KEY
);
$backend['links'] = [
'self' => (string)$apiUrl->withPath(
parseConfigValue(\App\API\Backend\Index::URL) . '/' . $backend['name']
),
];
$response['backends'][] = $backend;
}
return api_response(HTTP_STATUS::HTTP_OK, $response);
return api_response(HTTP_STATUS::HTTP_OK, $list);
}
}

38
src/API/Backends/UUid.php Normal file
View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace App\API\Backends;
use App\Libs\Attributes\Route\Route;
use App\Libs\DataUtil;
use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
final class UUid
{
use APITraits;
#[Route(['GET', 'POST'], Index::URL . '/uuid/{type}[/]', name: 'backends.get.unique_id')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($type = ag($args, 'type'))) {
return api_error('Invalid value for type path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getBasicClient($type, DataUtil::fromRequest($request, true));
return api_response(HTTP_STATUS::HTTP_OK, [
'identifier' => $client->getIdentifier(true)
]);
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\API\Backends;
use App\Libs\Attributes\Route\Route;
use App\Libs\DataUtil;
use App\Libs\Exceptions\InvalidArgumentException;
use App\Libs\HTTP_STATUS;
use App\Libs\Traits\APITraits;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Throwable;
final class Users
{
use APITraits;
#[Route(['GET', 'POST'], Index::URL . '/users/{type}[/]', name: 'backends.get.users')]
public function __invoke(iRequest $request, array $args = []): iResponse
{
if (null === ($type = ag($args, 'type'))) {
return api_error('Invalid value for type path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$client = $this->getBasicClient($type, DataUtil::fromRequest($request, true));
} catch (InvalidArgumentException $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$users = [];
foreach ($client->getUsersList() as $user) {
$users[] = [
'id' => $user['id'],
'name' => $user['name']
];
}
} catch (Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
return api_response(HTTP_STATUS::HTTP_OK, $users);
}
}

View File

@@ -27,7 +27,7 @@ final class Index
$this->pdo = $this->db->getPDO();
}
#[Get(self::URL . '[/]', name: 'history.index')]
#[Get(self::URL . '[/]', name: 'history')]
public function historyIndex(iRequest $request): iResponse
{
$es = fn(string $val) => $this->db->identifier($val);
@@ -334,22 +334,12 @@ final class Index
return api_error('Not found', HTTP_STATUS::HTTP_NOT_FOUND);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$item = $item->getAll();
$item[iState::COLUMN_WATCHED] = $entity->isWatched();
$item[iState::COLUMN_UPDATED] = makeDate($entity->updated);
$item = [
...$item,
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, ['history' => $item]);
return api_response(HTTP_STATUS::HTTP_OK, $item);
}
}

View File

@@ -24,7 +24,7 @@ final class Index
private const int DEFAULT_LIMIT = 1000;
private int $counter = 1;
#[Get(self::URL . '[/]', name: 'logs.list')]
#[Get(self::URL . '[/]', name: 'logs')]
public function logsList(iRequest $request): iResponse
{
$path = fixPath(Config::get('tmpDir') . '/logs');
@@ -38,7 +38,6 @@ final class Index
foreach (glob($path . '/*.*.log') as $file) {
preg_match('/(\w+)\.(\w+)\.log/i', basename($file), $matches);
$url = $apiUrl->withPath(parseConfigValue(self::URL . "/" . basename($file)));
$builder = [
'filename' => basename($file),
@@ -46,23 +45,19 @@ final class Index
'date' => $matches[2] ?? '??',
'size' => filesize($file),
'modified' => makeDate(filemtime($file)),
'urls' => [
'self' => (string)$url,
'stream' => (string)$url->withQuery($query),
],
];
$list[] = $builder;
}
return api_response(HTTP_STATUS::HTTP_OK, ['logs' => $list]);
return api_response(HTTP_STATUS::HTTP_OK, $list);
}
#[Get(Index::URL . '/{filename}[/]', name: 'logs.view')]
public function logView(iRequest $request, array $args = []): iResponse
{
if (null === ($filename = ag($args, 'filename'))) {
return api_error('Invalid value for id path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
return api_error('Invalid value for filename path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$path = realpath(fixPath(Config::get('tmpDir') . '/logs'));

View File

@@ -1,40 +0,0 @@
<?php
declare(strict_types=1);
namespace App\API\Plex;
use App\Backends\Plex\PlexClient;
use App\Libs\Attributes\Route\Post;
use App\Libs\DataUtil;
use App\Libs\HTTP_STATUS;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
use Symfony\Contracts\HttpClient\HttpClientInterface as iHttp;
final class Discover
{
public const string URL = '%{api.prefix}/plex/discover';
public function __construct(private iHttp $http)
{
}
#[Post(self::URL . '[/]', name: 'plex.discover')]
public function plexDiscover(iRequest $request): iResponse
{
$data = DataUtil::fromArray($request->getParsedBody());
if (null === ($token = $data->get('token'))) {
return api_error('No token was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
try {
$list = PlexClient::discover($this->http, $token);
} catch (\Throwable $e) {
return api_error($e->getMessage(), HTTP_STATUS::HTTP_INTERNAL_SERVER_ERROR);
}
return api_response(HTTP_STATUS::HTTP_OK, ag($list, 'list', []));
}
}

View File

@@ -22,11 +22,11 @@ final class Env
'WS_CACHE_URL'
];
private EnvFile $envfile;
private EnvFile $envFile;
public function __construct()
{
$this->envfile = (new EnvFile(file: Config::get('path') . '/config/.env', create: true));
$this->envFile = (new EnvFile(file: Config::get('path') . '/config/.env', create: true));
}
#[Get(self::URL . '[/]', name: 'system.env')]
@@ -35,12 +35,9 @@ final class Env
$response = [
'data' => [],
'file' => Config::get('path') . '/config/.env',
'links' => [
'self' => (string)$request->getUri()->withHost('')->withPort(0)->withScheme(''),
],
];
foreach ($this->envfile->getAll() as $key => $val) {
foreach ($this->envFile->getAll() as $key => $val) {
if (false === str_starts_with($key, 'WS_')) {
continue;
}
@@ -49,9 +46,6 @@ final class Env
'key' => $key,
'value' => $val,
'mask' => in_array($key, self::MASK),
'urls' => [
'self' => (string)$request->getUri()->withPath(parseConfigValue(self::URL . '/' . $key)),
],
];
}
@@ -70,13 +64,13 @@ final class Env
return api_error(r("Invalid key '{key}' was given.", ['key' => $key]), HTTP_STATUS::HTTP_BAD_REQUEST);
}
if (false === $this->envfile->has($key)) {
if (false === $this->envFile->has($key)) {
return api_error(r("Key '{key}' is not set.", ['key' => $key]), HTTP_STATUS::HTTP_NOT_FOUND);
}
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $key,
'value' => $this->envfile->get($key),
'value' => $this->envFile->get($key),
]);
}
@@ -94,7 +88,7 @@ final class Env
}
if ('DELETE' === $request->getMethod()) {
$this->envfile->remove($key);
$this->envFile->remove($key);
} else {
$params = DataUtil::fromRequest($request);
if (null === ($value = $params->get('value', null))) {
@@ -103,14 +97,14 @@ final class Env
]), HTTP_STATUS::HTTP_BAD_REQUEST);
}
$this->envfile->set($key, $value);
$this->envFile->set($key, $value);
}
$this->envfile->persist();
$this->envFile->persist();
return api_response(HTTP_STATUS::HTTP_OK, [
'key' => $key,
'value' => env($key, fn() => $this->envfile->get($key)),
'value' => $this->envFile->get($key, fn() => env($key)),
]);
}
}

View File

@@ -17,8 +17,6 @@ final class Supported
#[Get(self::URL . '[/]', name: 'system.supported')]
public function __invoke(iRequest $request): iResponse
{
return api_response(HTTP_STATUS::HTTP_OK, [
'supported' => array_keys(Config::get('supported')),
]);
return api_response(HTTP_STATUS::HTTP_OK, ['supported' => array_keys(Config::get('supported'))]);
}
}

View File

@@ -8,6 +8,7 @@ use App\Commands\System\TasksCommand;
use App\Libs\Attributes\Route\Get;
use App\Libs\Attributes\Route\Route;
use App\Libs\HTTP_STATUS;
use Cron\CronExpression;
use DateInterval;
use Psr\Http\Message\ResponseInterface as iResponse;
use Psr\Http\Message\ServerRequestInterface as iRequest;
@@ -28,29 +29,15 @@ final class Index
#[Get(self::URL . '[/]', name: 'tasks.index')]
public function tasksIndex(iRequest $request): iResponse
{
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$urlPath = rtrim($request->getUri()->getPath(), '/');
$queuedTasks = $this->cache->get('queued_tasks', []);
$response = [
'tasks' => [],
'queued' => $queuedTasks,
'links' => [
'self' => (string)$apiUrl,
],
];
foreach (TasksCommand::getTasks() as $task) {
$task = self::formatTask($task);
$task['links'] = [
'self' => (string)$apiUrl->withPath($urlPath . '/' . ag($task, 'name')),
'queue' => (string)$apiUrl->withPath($urlPath . '/' . ag($task, 'name') . '/queue'),
];
$task['queued'] = in_array(ag($task, 'name'), $queuedTasks);
$response['tasks'][] = $task;
}
@@ -87,17 +74,9 @@ final class Index
return api_response(HTTP_STATUS::HTTP_OK, ['queue' => $queuedTasks]);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('')->withUserInfo('');
$urlPath = parseConfigValue(Index::URL);
return api_response(HTTP_STATUS::HTTP_OK, [
'task' => $id,
'is_queued' => in_array($id, $queuedTasks),
'links' => [
'self' => (string)$apiUrl,
'task' => (string)$apiUrl->withPath($urlPath . '/' . $id),
'tasks' => (string)$apiUrl->withPath($urlPath),
],
]);
}
@@ -108,34 +87,27 @@ final class Index
return api_error('No id was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
}
$apiUrl = $request->getUri()->withHost('')->withPort(0)->withScheme('');
$task = TasksCommand::getTasks($id);
if (empty($task)) {
return api_error('Task not found.', HTTP_STATUS::HTTP_NOT_FOUND);
}
$response = [
...Index::formatTask($task),
'links' => [
'self' => (string)$apiUrl,
'list' => (string)$apiUrl->withPath(parseConfigValue(Index::URL)),
],
];
return api_response(HTTP_STATUS::HTTP_OK, ['task' => $response]);
return api_response(HTTP_STATUS::HTTP_OK, Index::formatTask($task));
}
private function formatTask(array $task): array
{
$isEnabled = (bool)ag($task, 'enabled', false);
$timer = ag($task, 'timer');
assert($timer instanceof CronExpression);
$item = [
'name' => ag($task, 'name'),
'description' => ag($task, 'description'),
'enabled' => true === $isEnabled,
'timer' => ag($task, 'timer')->getexpression(),
'timer' => $timer->getExpression(),
'next_run' => null,
'prev_run' => null,
'command' => ag($task, 'command'),
@@ -147,8 +119,13 @@ final class Index
}
if (true === $isEnabled) {
$item['next_run'] = makeDate(ag($task, 'timer')->getNextRunDate());
$item['prev_run'] = makeDate(ag($task, 'timer')->getPreviousRunDate());
try {
$item['next_run'] = makeDate($timer->getNextRunDate());
$item['prev_run'] = makeDate($timer->getPreviousRunDate());
} catch (\Exception) {
$item['next_run'] = null;
$item['prev_run'] = null;
}
}
return $item;