Remapped backend endpoints to /backend instead of /backends.
This commit is contained in:
157
src/API/Backend/Ignore.php
Normal file
157
src/API/Backend/Ignore.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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\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;
|
||||
use Throwable;
|
||||
|
||||
final class Ignore
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
private ConfigFile $file;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->file = new ConfigFile(Config::get('path') . '/config/ignore.yaml', type: 'yaml', autoCreate: true);
|
||||
}
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.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);
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($this->file->getAll() as $guid => $date) {
|
||||
$urlParts = parse_url($guid);
|
||||
|
||||
$backend = ag($urlParts, 'host');
|
||||
$type = ag($urlParts, 'scheme');
|
||||
$db = ag($urlParts, 'user');
|
||||
$id = ag($urlParts, 'pass');
|
||||
$scope = ag($urlParts, 'query');
|
||||
|
||||
if ($name !== $backend) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rule = makeIgnoreId($guid);
|
||||
|
||||
$list[] = [
|
||||
'rule' => (string)$rule,
|
||||
'type' => ucfirst($type),
|
||||
'backend' => $backend,
|
||||
'db' => $db,
|
||||
'id' => $id,
|
||||
'scoped' => null === $scope ? 'No' : 'Yes',
|
||||
'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);
|
||||
}
|
||||
|
||||
#[Delete(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.backend.ignoredIds.delete')]
|
||||
public function deleteRule(iRequest $request, array $args = []): iResponse
|
||||
{
|
||||
$params = DataUtil::fromRequest($request);
|
||||
|
||||
if (null === ($rule = $params->get('rule'))) {
|
||||
return api_error('No rule was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
checkIgnoreRule($rule);
|
||||
} catch (Throwable $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (!$this->file->has($rule)) {
|
||||
return api_error('Rule not found.', HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$this->file->delete($rule)->persist();
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK);
|
||||
}
|
||||
|
||||
#[Post(Index::URL . '/{name:backend}/ignore[/]', name: 'backends.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);
|
||||
}
|
||||
|
||||
$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'))) {
|
||||
$partial = [
|
||||
'type' => $params->get('type'),
|
||||
'backend' => $name,
|
||||
'db' => $params->get('db'),
|
||||
'id' => $params->get('id'),
|
||||
];
|
||||
|
||||
foreach ($partial as $k => $v) {
|
||||
if (empty($v)) {
|
||||
return api_error(r('No {key} was given.', ['key' => $k]), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
$partial['type'] = strtolower($partial['type']);
|
||||
|
||||
$rule = r('{type}://{db}:{id}@{backend}', $partial);
|
||||
|
||||
if (null !== ($scoped = $params->get('scoped'))) {
|
||||
$rule .= '?id=' . $scoped;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
checkIgnoreRule($rule);
|
||||
$id = makeIgnoreId($rule);
|
||||
} catch (Throwable $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
if (true === $this->file->has((string)$id)) {
|
||||
return api_error('Rule already exists.', HTTP_STATUS::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
if (true === $this->file->has((string)$id->withQuery(''))) {
|
||||
return api_error('Global rule already exists.', HTTP_STATUS::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$this->file->set((string)$id, time())->persist();
|
||||
return api_response(HTTP_STATUS::HTTP_CREATED);
|
||||
}
|
||||
}
|
||||
45
src/API/Backend/Index.php
Normal file
45
src/API/Backend/Index.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend;
|
||||
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
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;
|
||||
|
||||
public const string URL = '%{api.prefix}/backend';
|
||||
|
||||
#[Get(self::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 id 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);
|
||||
}
|
||||
|
||||
$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]);
|
||||
}
|
||||
}
|
||||
59
src/API/Backend/Info.php
Normal file
59
src/API/Backend/Info.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Throwable;
|
||||
|
||||
final class Info
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/info[/]', name: 'backends.backend.info')]
|
||||
public function backendsView(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);
|
||||
|
||||
if (true === (bool)$params->get('raw', false)) {
|
||||
$opts[Options::RAW_RESPONSE] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $client->getInfo($opts);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
87
src/API/Backend/Library/Ignore.php
Normal file
87
src/API/Backend/Library/Ignore.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend\Library;
|
||||
|
||||
use App\API\Backend\Index as BackendsIndex;
|
||||
use App\Libs\Attributes\Route\Route;
|
||||
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 Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
|
||||
final class Ignore
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Route(['POST', 'DELETE'], BackendsIndex::URL . '/{name:backend}/library[/]', name: 'backends.library.ignore')]
|
||||
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);
|
||||
}
|
||||
|
||||
if (null === ($id = DataUtil::fromRequest($request, true)->get('id', null))) {
|
||||
return api_error('No library id was given.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$remove = 'DELETE' === $request->getMethod();
|
||||
|
||||
$config = ConfigFile::open(Config::get('backends_file'), 'yaml');
|
||||
|
||||
if (null === $config->get($name)) {
|
||||
return api_error(r("Backend '{backend}' not found.", ['backend' => $name]), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$ignoreIds = array_map(
|
||||
fn($v) => trim($v),
|
||||
explode(',', (string)$config->get("{$name}.options." . Options::IGNORE, ''))
|
||||
);
|
||||
|
||||
$mode = !(true === $remove);
|
||||
if ($mode === in_array($id, $ignoreIds)) {
|
||||
return api_error(r("Library id '{id}' is {message} ignored.", [
|
||||
'id' => $id,
|
||||
'message' => $remove ? "not" : 'already',
|
||||
]), $remove ? HTTP_STATUS::HTTP_NOT_FOUND : HTTP_STATUS::HTTP_CONFLICT);
|
||||
}
|
||||
|
||||
$found = false;
|
||||
|
||||
$libraries = $this->getClient(name: $name)->listLibraries();
|
||||
foreach ($libraries as &$library) {
|
||||
if ((string)ag($library, 'id') === (string)$id) {
|
||||
$ignoreIds[] = $id;
|
||||
$library['ignored'] = !$remove;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $found) {
|
||||
return api_error(r("The library id '{id}' is incorrect.", ['id' => $name]), HTTP_STATUS::HTTP_NOT_FOUND, [
|
||||
'possible_ids' => array_column($libraries, 'id'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (true === $remove) {
|
||||
$ignoreIds = array_diff($ignoreIds, [$id]);
|
||||
}
|
||||
|
||||
$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}"),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
44
src/API/Backend/Library/Index.php
Normal file
44
src/API/Backend/Library/Index.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
88
src/API/Backend/Library/Mismatched.php
Normal file
88
src/API/Backend/Library/Mismatched.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend\Library;
|
||||
|
||||
use App\API\Backend\Index as BackendsIndex;
|
||||
use App\Commands\Backend\Library\MismatchCommand;
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
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;
|
||||
|
||||
final class Mismatched
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(BackendsIndex::URL . '/{name:backend}/mismatched[/[{id}[/]]]', name: 'backends.library.mismatched')]
|
||||
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);
|
||||
}
|
||||
|
||||
$params = DataUtil::fromArray($request->getQueryParams());
|
||||
|
||||
$backendOpts = $opts = $list = [];
|
||||
|
||||
if ($params->get('timeout')) {
|
||||
$backendOpts = ag_set($backendOpts, 'client.timeout', (float)$params->get('timeout'));
|
||||
}
|
||||
|
||||
if ($params->get('raw')) {
|
||||
$opts[Options::RAW_RESPONSE] = true;
|
||||
}
|
||||
|
||||
$percentage = (float)$params->get('percentage', MismatchCommand::DEFAULT_PERCENT);
|
||||
$method = $params->get('method', MismatchCommand::METHODS[0]);
|
||||
|
||||
if (false === in_array($method, MismatchCommand::METHODS, true)) {
|
||||
return api_error('Invalid comparison method.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getClient(name: $name, config: $backendOpts);
|
||||
} catch (RuntimeException $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
if (null !== ($id = ag($args, 'id'))) {
|
||||
$ids[] = $id;
|
||||
} else {
|
||||
foreach ($client->listLibraries() as $library) {
|
||||
if (false === (bool)ag($library, 'supported') || true === (bool)ag($library, 'ignored')) {
|
||||
continue;
|
||||
}
|
||||
$ids[] = ag($library, 'id');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ids as $libraryId) {
|
||||
foreach ($client->getLibrary(id: $libraryId, opts: $opts) as $item) {
|
||||
$processed = MismatchCommand::compare(item: $item, method: $method);
|
||||
|
||||
if (empty($processed) || $processed['percent'] >= $percentage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
76
src/API/Backend/Library/Unmatched.php
Normal file
76
src/API/Backend/Library/Unmatched.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?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\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;
|
||||
|
||||
final class Unmatched
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(BackendsIndex::URL . '/{name:backend}/unmatched[/[{id}[/]]]', name: 'backends.library.unmatched')]
|
||||
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);
|
||||
}
|
||||
|
||||
$params = DataUtil::fromArray($request->getQueryParams());
|
||||
|
||||
$backendOpts = $opts = $list = [];
|
||||
|
||||
if ($params->get('timeout')) {
|
||||
$backendOpts = ag_set($backendOpts, 'client.timeout', (float)$params->get('timeout'));
|
||||
}
|
||||
|
||||
if ($params->get('raw')) {
|
||||
$opts[Options::RAW_RESPONSE] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getClient(name: $name, config: $backendOpts);
|
||||
} catch (RuntimeException $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
if (null !== ($id = ag($args, 'id'))) {
|
||||
$ids[] = $id;
|
||||
} else {
|
||||
foreach ($client->listLibraries() as $library) {
|
||||
if (false === (bool)ag($library, 'supported') || true === (bool)ag($library, 'ignored')) {
|
||||
continue;
|
||||
}
|
||||
$ids[] = ag($library, 'id');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ids as $libraryId) {
|
||||
foreach ($client->getLibrary(id: $libraryId, opts: $opts) as $item) {
|
||||
if (null === ($externals = ag($item, 'guids', null)) || empty($externals)) {
|
||||
$list[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
71
src/API/Backend/PartialUpdate.php
Normal file
71
src/API/Backend/PartialUpdate.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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)),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
78
src/API/Backend/Search.php
Normal file
78
src/API/Backend/Search.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend;
|
||||
|
||||
use App\Libs\Attributes\Route\Get;
|
||||
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;
|
||||
|
||||
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
|
||||
{
|
||||
if (null === ($name = ag($args, 'name'))) {
|
||||
return api_error('Invalid value for name path parameter.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$params = DataUtil::fromRequest($request, true);
|
||||
|
||||
$id = ag($args, 'id', $params->get('id', null));
|
||||
$query = $params->get('query', null);
|
||||
|
||||
if (null === $id && null === $query) {
|
||||
return api_error('No search id or query string was provided.', HTTP_STATUS::HTTP_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$backend = $this->getClient(name: $name);
|
||||
} catch (RuntimeException $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (null !== $id) {
|
||||
$data = $backend->searchId($id, [Options::RAW_RESPONSE => (bool)$params->get('raw', false)]);
|
||||
} else {
|
||||
$data = $backend->search(
|
||||
query: $query,
|
||||
limit: (int)$params->get('limit', 25),
|
||||
opts: [Options::RAW_RESPONSE => (bool)$params->get('raw', false)]
|
||||
);
|
||||
}
|
||||
|
||||
if (count($data) < 1) {
|
||||
return api_error(r("No results are found for '{query}'.", [
|
||||
'query' => $id ?? $query
|
||||
]), 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),
|
||||
],
|
||||
];
|
||||
|
||||
if (null === $id && $query) {
|
||||
$response['options']['limit'] = (int)$params->get('limit', 25);
|
||||
}
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK, $response);
|
||||
}
|
||||
|
||||
}
|
||||
59
src/API/Backend/Sessions.php
Normal file
59
src/API/Backend/Sessions.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Throwable;
|
||||
|
||||
final class Sessions
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/sessions[/]', name: 'backends.backend.sessions')]
|
||||
public function backendsView(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);
|
||||
|
||||
if (true === (bool)$params->get('raw', false)) {
|
||||
$opts[Options::RAW_RESPONSE] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$sessions = $client->getSessions($opts);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
63
src/API/Backend/Users.php
Normal file
63
src/API/Backend/Users.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Throwable;
|
||||
|
||||
final class Users
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/users[/]', name: 'backends.backend.users')]
|
||||
public function backendsView(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);
|
||||
|
||||
if (true === (bool)$params->get('tokens', false)) {
|
||||
$opts['tokens'] = true;
|
||||
}
|
||||
|
||||
if (true === (bool)$params->get('raw', false)) {
|
||||
$opts[Options::RAW_RESPONSE] = true;
|
||||
}
|
||||
|
||||
try {
|
||||
$users = $client->getUsersList($opts);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
59
src/API/Backend/Version.php
Normal file
59
src/API/Backend/Version.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
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;
|
||||
use Throwable;
|
||||
|
||||
final class Version
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
#[Get(Index::URL . '/{name:backend}/version[/]', name: 'backends.backend.info')]
|
||||
public function backendsView(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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
265
src/API/Backend/Webhooks.php
Normal file
265
src/API/Backend/Webhooks.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\API\Backend;
|
||||
|
||||
use App\Libs\Attributes\Route\Route;
|
||||
use App\Libs\Config;
|
||||
use App\Libs\Entity\StateInterface as iState;
|
||||
use App\Libs\Exceptions\RuntimeException;
|
||||
use App\Libs\Extends\LogMessageProcessor;
|
||||
use App\Libs\HTTP_STATUS;
|
||||
use App\Libs\Options;
|
||||
use App\Libs\Traits\APITraits;
|
||||
use App\Libs\Uri;
|
||||
use DateInterval;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Level;
|
||||
use Monolog\Logger;
|
||||
use Psr\Http\Message\ResponseInterface as iResponse;
|
||||
use Psr\Http\Message\ServerRequestInterface as iRequest;
|
||||
use Psr\Log\LoggerInterface as iLogger;
|
||||
use Psr\SimpleCache\CacheInterface as iCache;
|
||||
use Psr\SimpleCache\InvalidArgumentException;
|
||||
|
||||
final class Webhooks
|
||||
{
|
||||
use APITraits;
|
||||
|
||||
private iLogger $accessLog;
|
||||
|
||||
public function __construct(private iCache $cache)
|
||||
{
|
||||
$this->accessLog = new Logger(name: 'http', 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));
|
||||
}
|
||||
|
||||
if (true === inContainer()) {
|
||||
$this->accessLog->pushHandler(new StreamHandler('php://stderr', $level, true));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a webhook request from a backend.
|
||||
*
|
||||
* @param iRequest $request The incoming request object.
|
||||
* @param array $args The request path arguments.
|
||||
*
|
||||
* @return iResponse The response object.
|
||||
* @throws InvalidArgumentException if cache key is invalid.
|
||||
*/
|
||||
#[Route(['POST', 'PUT'], Index::URL . '/{name:backend}/webhook[/]', name: 'webhooks.receive')]
|
||||
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 {
|
||||
$backend = $this->getBackends(name: $name);
|
||||
if (empty($backend)) {
|
||||
throw new RuntimeException(r("Backend '{backend}' not found.", ['backend ' => $name]));
|
||||
}
|
||||
|
||||
$backend = array_pop($backend);
|
||||
|
||||
$client = $this->getClient(name: $name);
|
||||
} catch (RuntimeException $e) {
|
||||
return api_error($e->getMessage(), HTTP_STATUS::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (true === Config::get('webhook.dumpRequest')) {
|
||||
saveRequestPayload(clone $request);
|
||||
}
|
||||
|
||||
$request = $client->processRequest($request);
|
||||
$attr = $request->getAttributes();
|
||||
|
||||
if (null !== ($userId = ag($backend, 'user', null)) && true === (bool)ag($backend, 'webhook.match.user')) {
|
||||
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');
|
||||
}
|
||||
|
||||
if (false === hash_equals((string)$userId, (string)$requestUser)) {
|
||||
$message = r('Request user id [{req_user}] does not match configured value [{config_user}]', [
|
||||
'req_user' => $requestUser ?? 'NOT SET',
|
||||
'config_user' => $userId,
|
||||
]);
|
||||
$this->write($request, Level::Info, $message);
|
||||
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== ($uuid = ag($backend, 'uuid', null)) && true === (bool)ag($backend, 'webhook.match.uuid')) {
|
||||
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');
|
||||
}
|
||||
|
||||
if (false === hash_equals((string)$uuid, (string)$requestBackendId)) {
|
||||
$message = r('Request backend unique id [{req_uid}] does not match backend uuid [{config_uid}].', [
|
||||
'req_uid' => $requestBackendId ?? 'NOT SET',
|
||||
'config_uid' => $uuid,
|
||||
]);
|
||||
$this->write($request, Level::Info, $message);
|
||||
return api_error($message, HTTP_STATUS::HTTP_BAD_REQUEST)->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
}
|
||||
|
||||
if (true === (bool)ag($backend, 'import.enabled')) {
|
||||
if (true === ag_exists($backend, 'options.' . Options::IMPORT_METADATA_ONLY)) {
|
||||
$backend = ag_delete($backend, 'options.' . Options::IMPORT_METADATA_ONLY);
|
||||
}
|
||||
}
|
||||
|
||||
$metadataOnly = true === (bool)ag($backend, 'options.' . Options::IMPORT_METADATA_ONLY);
|
||||
|
||||
if (true !== $metadataOnly && true !== (bool)ag($backend, 'import.enabled')) {
|
||||
$response = api_response(HTTP_STATUS::HTTP_NOT_ACCEPTABLE);
|
||||
$this->write($request, Level::Error, r('Import are disabled for [{backend}].', [
|
||||
'backend' => $client->getName(),
|
||||
]), forceContext: true);
|
||||
|
||||
return $response->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
|
||||
$entity = $client->parseWebhook($request);
|
||||
|
||||
if (true === (bool)ag($backend, 'options.' . Options::DUMP_PAYLOAD)) {
|
||||
saveWebhookPayload($entity, $request);
|
||||
}
|
||||
|
||||
if (!$entity->hasGuids() && !$entity->hasRelativeGuid()) {
|
||||
$this->write(
|
||||
$request,
|
||||
Level::Info,
|
||||
'Ignoring [{backend}] {item.type} [{item.title}]. No valid/supported external ids.',
|
||||
[
|
||||
'backend' => $entity->via,
|
||||
'item' => [
|
||||
'title' => $entity->getName(),
|
||||
'type' => $entity->type,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED)->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
|
||||
if ((0 === (int)$entity->episode || null === $entity->season) && $entity->isEpisode()) {
|
||||
$this->write(
|
||||
$request,
|
||||
Level::Notice,
|
||||
'Ignoring [{backend}] {item.type} [{item.title}]. No episode/season number present.',
|
||||
[
|
||||
'backend' => $entity->via,
|
||||
'item' => [
|
||||
'title' => $entity->getName(),
|
||||
'type' => $entity->type,
|
||||
'season' => (string)($entity->season ?? 'None'),
|
||||
'episode' => (string)($entity->episode ?? 'None'),
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_NOT_MODIFIED)->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
|
||||
$items = $this->cache->get('requests', []);
|
||||
|
||||
$itemId = r('{type}://{id}:{tainted}@{backend}', [
|
||||
'type' => $entity->type,
|
||||
'backend' => $entity->via,
|
||||
'tainted' => $entity->isTainted() ? 'tainted' : 'untainted',
|
||||
'id' => ag($entity->getMetadata($entity->via), iState::COLUMN_ID, '??'),
|
||||
]);
|
||||
|
||||
$items[$itemId] = [
|
||||
'options' => [
|
||||
Options::IMPORT_METADATA_ONLY => $metadataOnly,
|
||||
],
|
||||
'entity' => $entity,
|
||||
];
|
||||
|
||||
$this->cache->set('requests', $items, new DateInterval('P3D'));
|
||||
|
||||
if (false === $metadataOnly && true === $entity->hasPlayProgress()) {
|
||||
$progress = $this->cache->get('progress', []);
|
||||
$progress[$itemId] = $entity;
|
||||
$this->cache->set('progress', $progress, new DateInterval('P1D'));
|
||||
}
|
||||
|
||||
$this->write($request, Level::Info, 'Queued [{backend}: {event}] {item.type} [{item.title}].', [
|
||||
'backend' => $entity->via,
|
||||
'event' => ag($entity->getExtra($entity->via), iState::COLUMN_EXTRA_EVENT),
|
||||
'has_progress' => $entity->hasPlayProgress() ? 'Yes' : 'No',
|
||||
'item' => [
|
||||
'title' => $entity->getName(),
|
||||
'type' => $entity->type,
|
||||
'played' => $entity->isWatched() ? 'Yes' : 'No',
|
||||
'queue_id' => $itemId,
|
||||
'progress' => $entity->hasPlayProgress() ? $entity->getPlayProgress() : null,
|
||||
]
|
||||
]
|
||||
);
|
||||
|
||||
return api_response(HTTP_STATUS::HTTP_OK)->withHeader('X-Log-Response', '0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log entry to the access log.
|
||||
*
|
||||
* @param iRequest $request The incoming request object.
|
||||
* @param int|string|Level $level The log level or priority.
|
||||
* @param string $message The log message.
|
||||
* @param array $context Additional data/context for the log entry.
|
||||
*/
|
||||
private function write(
|
||||
iRequest $request,
|
||||
int|string|Level $level,
|
||||
string $message,
|
||||
array $context = [],
|
||||
bool $forceContext = false
|
||||
): void {
|
||||
$params = $request->getServerParams();
|
||||
|
||||
$uri = new Uri((string)ag($params, 'REQUEST_URI', '/'));
|
||||
|
||||
if (false === empty($uri->getQuery())) {
|
||||
$query = [];
|
||||
parse_str($uri->getQuery(), $query);
|
||||
if (true === ag_exists($query, 'apikey')) {
|
||||
$query['apikey'] = 'api_key_removed';
|
||||
$uri = $uri->withQuery(http_build_query($query));
|
||||
}
|
||||
}
|
||||
|
||||
$context = array_replace_recursive([
|
||||
'request' => [
|
||||
'method' => $request->getMethod(),
|
||||
'id' => ag($params, 'X_REQUEST_ID'),
|
||||
'ip' => getClientIp($request),
|
||||
'agent' => ag($params, 'HTTP_USER_AGENT'),
|
||||
'uri' => (string)$uri,
|
||||
],
|
||||
], $context);
|
||||
|
||||
if (($attributes = $request->getAttributes()) && count($attributes) >= 1) {
|
||||
$context['attributes'] = $attributes;
|
||||
}
|
||||
|
||||
if (true === (Config::get('logs.context') || $forceContext)) {
|
||||
$this->accessLog->log($level, $message, $context);
|
||||
} else {
|
||||
$this->accessLog->log($level, r($message, $context));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user