Added webhook event and type to nginx log output.

This commit is contained in:
Abdulmhsen B. A. A
2022-04-18 20:04:06 +03:00
parent c1646c7dcb
commit 1e73d0cd91
9 changed files with 236 additions and 202 deletions

View File

@@ -6,6 +6,7 @@ namespace App\Libs;
use App\Cli;
use App\Libs\Extends\ConsoleOutput;
use App\Libs\Storage\StorageInterface;
use Closure;
use Laminas\HttpHandlerRunner\Emitter\EmitterInterface;
use Laminas\HttpHandlerRunner\Emitter\SapiEmitter;
@@ -123,12 +124,14 @@ final class Initializer
/**
* Handle HTTP Request.
*
* @param Closure(ServerRequestInterface): ResponseInterface $fn
* @param ServerRequestInterface|null $request
* @param EmitterInterface|null $emitter
* @param null|Closure(ServerRequestInterface): ResponseInterface $fn
*/
public function runHttp(
Closure $fn,
ServerRequestInterface|null $request = null,
EmitterInterface|null $emitter = null
EmitterInterface|null $emitter = null,
Closure|null $fn = null,
): void {
$emitter = $emitter ?? new SapiEmitter();
@@ -138,7 +141,7 @@ final class Initializer
}
try {
$response = $fn($request);
$response = null === $fn ? $this->defaultHttpServer($request) : $fn($request);
} catch (Throwable $e) {
Container::get(LoggerInterface::class)->error(
$e->getMessage(),
@@ -153,6 +156,217 @@ final class Initializer
$emitter->emit($response);
}
private function defaultHttpServer(ServerRequestInterface $request): ResponseInterface
{
$log = [];
$logger = Container::get(LoggerInterface::class);
try {
if (true === (bool)env('WS_REQUEST_DEBUG') || null !== ag($request->getQueryParams(), 'rdebug')) {
saveRequestPayload($request);
}
$request = preServeHttpRequest($request);
// -- get apikey from header or query.
$apikey = $request->getHeaderLine('x-apikey');
if (empty($apikey)) {
$apikey = ag($request->getQueryParams(), 'apikey', '');
if (empty($apikey)) {
$log[] = 'No api key in headers or query';
throw new HttpException('No API key was given.', 400);
}
}
$server = [];
Config::get('servers', []);
$validUser = $validUUid = null;
// -- Find Server
foreach (Config::get('servers', []) as $name => $info) {
if (null === ag($info, 'webhook.token')) {
continue;
}
if (!hash_equals(ag($info, 'webhook.token'), $apikey)) {
continue;
}
$userId = ag($info, 'user', null);
if (true === (true === ag($info, 'webhook.match.user') && null !== $userId)) {
if (null === ($requestUser = $request->getAttribute('USER_ID', null))) {
$validUser = false;
$log[] = 'Request user is not set';
continue;
}
if ((string)$userId !== (string)$requestUser) {
$validUser = false;
$log[] = sprintf(
'Request user [%s] does not match config user [%s]',
$requestUser ?? 'NO USER_ID',
$userId
);
continue;
}
$validUser = true;
}
$uuid = ag($info, 'uuid', null);
if (true === (true === ag($info, 'webhook.match.uuid') && null !== $uuid)) {
if (null === ($requestServerId = $request->getAttribute('SERVER_ID', null))) {
$validUUid = false;
$log[] = 'Request server unique id is not set';
continue;
}
if ((string)$uuid !== (string)$requestServerId) {
$validUUid = false;
$log[] = sprintf(
'Request UUID [%s] does not match config UUID [%s]',
$requestServerId ?? 'NO SERVER_ID',
$uuid
);
continue;
}
$validUUid = true;
}
$server = array_replace_recursive(['name' => $name], $info);
break;
}
if (empty($server)) {
if (false === $validUser) {
$message = 'API key is valid, User checks failed.';
} elseif (false === $validUUid) {
$message = 'API key and user check is valid, Server unique id checks failed.';
} else {
$message = 'Invalid API key was given.';
}
throw new HttpException($message, 401);
}
if (true !== ag($server, 'webhook.import')) {
$log[] = 'Import disabled for this server';
throw new HttpException(
sprintf(
'Import via webhook for this server \'%s\' is disabled.',
ag($server, 'name')
),
500
);
}
try {
$server['class'] = makeServer($server, $server['name']);
} catch (RuntimeException $e) {
$log[] = 'Creating Instance of the Backend has failed.';
throw new HttpException($e->getMessage(), 500);
}
$entity = $server['class']->parseWebhook($request);
if (!$entity->hasGuids()) {
return new Response(status: 204, headers: [
'X-Status' => 'No GUIDs.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
$storage = Container::get(StorageInterface::class);
if (null === ($backend = $storage->get($entity))) {
$entity = $storage->insert($entity);
queuePush($entity);
return jsonResponse(status: 200, body: $entity->getAll(), headers: [
'X-Status' => 'Added new entity.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
if (true === $entity->isTainted()) {
if ($backend->apply($entity, guidOnly: true)->isChanged()) {
if (!empty($entity->meta)) {
$backend->meta = $entity->meta;
}
$backend = $storage->update($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'Event is tainted. Only GUIDs updated.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
return new Response(status: 200, headers: [
'X-Status' => 'Nothing updated, entity state is tainted.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
if ($backend->updated > $entity->updated) {
if ($backend->apply($entity, guidOnly: true)->isChanged()) {
if (!empty($entity->meta)) {
$backend->meta = $entity->meta;
}
$backend = $storage->update($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'No watch state updated. Only GUIDs updated.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
return new Response(status: 200, headers: [
'X-Status' => 'Entity date is older than what available in storage.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
if ($backend->apply($entity)->isChanged()) {
$backend = $storage->update($backend);
queuePush($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'Item Queued.',
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
return new Response(status: 200, headers: ['X-Status' => 'Entity is unchanged.']);
} catch (HttpException $e) {
if (200 === $e->getCode()) {
return new Response(status: $e->getCode(), headers: [
'X-Status' => $e->getMessage(),
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
$logger->error($e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'attributes' => $request->getAttributes(),
'log' => $log,
]);
return jsonResponse($e->getCode(), ['error' => true, 'message' => $e->getMessage()], [
'X-Status' => $e->getMessage(),
'X-WH-Type' => $request->getAttribute('WH_TYPE', 'not_set'),
'X-WH-Event' => $request->getAttribute('WH_EVENT', 'not_set'),
]);
}
}
private function createDirectories(): void
{
$dirList = __DIR__ . '/../../config/directories.php';

View File

@@ -70,6 +70,8 @@ class EmbyServer extends JellyfinServer
'SERVER_VERSION' => afterLast($userAgent, '/'),
'USER_ID' => ag($json, 'User.Id', ''),
'USER_NAME' => ag($json, 'User.Name', ''),
'WH_EVENT' => ag($json, 'Event', 'not_set'),
'WH_TYPE' => ag($json, 'Item.Type', 'not_set'),
];
foreach ($attributes as $key => $val) {

View File

@@ -240,6 +240,8 @@ class JellyfinServer implements ServerInterface
'SERVER_VERSION' => afterLast($userAgent, '/'),
'USER_ID' => ag($json, 'UserId', ''),
'USER_NAME' => ag($json, 'NotificationUsername', ''),
'WH_EVENT' => ag($json, 'NotificationType', 'not_set'),
'WH_TYPE' => ag($json, 'ItemType', 'not_set'),
];
foreach ($attributes as $key => $val) {

View File

@@ -242,6 +242,8 @@ class PlexServer implements ServerInterface
'SERVER_VERSION' => afterLast($userAgent, '/'),
'USER_ID' => ag($json, 'Account.id', ''),
'USER_NAME' => ag($json, 'Account.title', ''),
'WH_EVENT' => ag($json, 'event', 'not_set'),
'WH_TYPE' => ag($json, 'Metadata.type', 'not_set'),
];
foreach ($attributes as $key => $val) {

View File

@@ -6,9 +6,7 @@ use App\Libs\Config;
use App\Libs\Container;
use App\Libs\Entity\StateInterface;
use App\Libs\Extends\Date;
use App\Libs\HttpException;
use App\Libs\Servers\ServerInterface;
use App\Libs\Storage\StorageInterface;
use Nyholm\Psr7\Response;
use Nyholm\Psr7\Uri;
use Psr\Http\Message\ResponseInterface;
@@ -309,198 +307,6 @@ if (!function_exists('preServeHttpRequest')) {
}
}
if (!function_exists('serveHttpRequest')) {
function serveHttpRequest(ServerRequestInterface $request): ResponseInterface
{
$log = [];
$logger = Container::get(LoggerInterface::class);
try {
if (true === (bool)env('WS_REQUEST_DEBUG') || null !== ag($request->getQueryParams(), 'rdebug')) {
saveRequestPayload($request);
}
$request = preServeHttpRequest($request);
// -- get apikey from header or query.
$apikey = $request->getHeaderLine('x-apikey');
if (empty($apikey)) {
$apikey = ag($request->getQueryParams(), 'apikey', '');
if (empty($apikey)) {
$log[] = 'No api key in headers or query';
throw new HttpException('No API key was given.', 400);
}
}
$server = [];
Config::get('servers', []);
$validUser = $validUUid = null;
// -- Find Server
foreach (Config::get('servers', []) as $name => $info) {
if (null === ag($info, 'webhook.token')) {
continue;
}
if (!hash_equals(ag($info, 'webhook.token'), $apikey)) {
continue;
}
$userId = ag($info, 'user', null);
if (true === (true === ag($info, 'webhook.match.user') && null !== $userId)) {
if (null === ($requestUser = $request->getAttribute('USER_ID', null))) {
$validUser = false;
$log[] = 'Request user is not set';
continue;
}
if ((string)$userId !== (string)$requestUser) {
$validUser = false;
$log[] = sprintf('Request user [%s] does not match config user [%s]', $requestUser, $userId);
continue;
}
$validUser = true;
}
$uuid = ag($info, 'uuid', null);
if (true === (true === ag($info, 'webhook.match.uuid') && null !== $uuid)) {
if (null === ($requestServerId = $request->getAttribute('SERVER_ID', null))) {
$validUUid = false;
$log[] = 'Request server unique id is not set';
continue;
}
if ((string)$uuid !== (string)$requestServerId) {
$validUUid = false;
$log[] = sprintf('Request UUID [%s] does not match config UUID [%s]', $requestServerId, $uuid);
continue;
}
$validUUid = true;
}
$server = array_replace_recursive(['name' => $name], $info);
break;
}
if (empty($server)) {
if (false === $validUser) {
$message = 'API key is valid, User checks failed.';
} elseif (false === $validUUid) {
$message = 'API key and user check is valid, Server unique id checks failed.';
} else {
$message = 'Invalid API key was given.';
}
throw new HttpException($message, 401);
}
if (true !== ag($server, 'webhook.import')) {
$log[] = 'Import disabled for this server';
throw new HttpException(
sprintf(
'Import via webhook for this server \'%s\' is disabled.',
ag($server, 'name')
),
500
);
}
try {
$server['class'] = makeServer($server, $server['name']);
} catch (RuntimeException $e) {
$log[] = 'Creating Instance of the Backend has failed.';
throw new HttpException($e->getMessage(), 500);
}
$entity = $server['class']->parseWebhook($request);
if (!$entity->hasGuids()) {
return new Response(status: 204, headers: ['X-Status' => 'No GUIDs.']);
}
$storage = Container::get(StorageInterface::class);
if (null === ($backend = $storage->get($entity))) {
$entity = $storage->insert($entity);
queuePush($entity);
return jsonResponse(status: 200, body: $entity->getAll(), headers: [
'X-Status' => 'Added new entity.'
]);
}
if (true === $entity->isTainted()) {
if ($backend->apply($entity, guidOnly: true)->isChanged()) {
if (!empty($entity->meta)) {
$backend->meta = $entity->meta;
}
$backend = $storage->update($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'Event is tainted. Only GUIDs updated.',
]);
}
return new Response(
status: 200,
headers: ['X-Status' => 'Nothing updated, entity state is tainted.']
);
}
if ($backend->updated > $entity->updated) {
if ($backend->apply($entity, guidOnly: true)->isChanged()) {
if (!empty($entity->meta)) {
$backend->meta = $entity->meta;
}
$backend = $storage->update($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'No watch state updated. Only GUIDs updated.',
]);
}
return new Response(
status: 200,
headers: ['X-Status' => 'Entity date is older than what available in storage.']
);
}
if ($backend->apply($entity)->isChanged()) {
$backend = $storage->update($backend);
queuePush($backend);
return jsonResponse(status: 200, body: $backend->getAll(), headers: [
'X-Status' => 'Item Queued.',
]);
}
return new Response(status: 200, headers: ['X-Status' => 'Entity is unchanged.']);
} catch (HttpException $e) {
if (200 === $e->getCode()) {
return new Response(status: $e->getCode(), headers: ['X-Status' => $e->getMessage()]);
}
$logger->error($e->getMessage(), [
'file' => $e->getFile(),
'line' => $e->getLine(),
'attributes' => $request->getAttributes(),
'log' => $log,
]);
return jsonResponse(
status: $e->getCode(),
body: [
'error' => true,
'message' => $e->getMessage()
],
headers: [
'X-Status' => $e->getMessage(),
]
);
}
}
}
if (!function_exists('queuePush')) {
function queuePush(StateInterface $entity): void
{