Version 5.1 - All the GraphQL #32
@ -27,6 +27,7 @@ use Aviat\AnimeClient\API\{
|
||||
};
|
||||
use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||
use Aviat\Ion\Event;
|
||||
|
||||
use Throwable;
|
||||
use const PHP_SAPI;
|
||||
@ -66,6 +67,8 @@ final class Auth {
|
||||
$this->segment = $container->get('session')
|
||||
->getSegment(SESSION_SEGMENT);
|
||||
$this->model = $container->get('kitsu-model');
|
||||
|
||||
Event::on('::unauthorized::', [$this, 'reAuthenticate']);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -87,6 +90,28 @@ final class Auth {
|
||||
return $this->storeAuth($auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the call to re-authenticate with the existing refresh token
|
||||
*
|
||||
* @param string $refreshToken
|
||||
* @return boolean
|
||||
* @throws InvalidArgumentException
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function reAuthenticate(?string $refreshToken): bool
|
||||
{
|
||||
$refreshToken ??= $this->getAuthToken();
|
||||
|
||||
if (empty($refreshToken))
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$auth = $this->model->reAuthenticate($refreshToken);
|
||||
|
||||
return $this->storeAuth($auth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current user is authenticated
|
||||
*
|
||||
@ -110,7 +135,7 @@ final class Auth {
|
||||
/**
|
||||
* Retrieve the authentication token from the session
|
||||
*
|
||||
* @return string|false
|
||||
* @return string
|
||||
*/
|
||||
private function getAuthToken(): ?string
|
||||
{
|
||||
@ -146,22 +171,14 @@ final class Auth {
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the call to re-authenticate with the existing refresh token
|
||||
*
|
||||
* @param string $token
|
||||
* @return boolean
|
||||
* @throws InvalidArgumentException
|
||||
* @throws Throwable
|
||||
*/
|
||||
private function reAuthenticate(string $token): bool
|
||||
private function getRefreshToken(): ?string
|
||||
{
|
||||
$auth = $this->model->reAuthenticate($token);
|
||||
|
||||
return $this->storeAuth($auth);
|
||||
return (PHP_SAPI === 'cli')
|
||||
? $this->cacheGet(K::AUTH_TOKEN_REFRESH_CACHE_KEY, NULL)
|
||||
: $this->segment->get('refresh_token');
|
||||
}
|
||||
|
||||
private function storeAuth($auth): bool
|
||||
private function storeAuth(bool $auth): bool
|
||||
{
|
||||
if (FALSE !== $auth)
|
||||
{
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
namespace Aviat\AnimeClient\API\Kitsu;
|
||||
|
||||
use Aviat\AnimeClient\Enum\EventType;
|
||||
use function in_array;
|
||||
use const PHP_SAPI;
|
||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||
|
||||
@ -24,10 +26,8 @@ use function Aviat\AnimeClient\getResponse;
|
||||
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Client\Response;
|
||||
use Aviat\AnimeClient\API\{
|
||||
FailedResponseException,
|
||||
Kitsu as K
|
||||
};
|
||||
use Aviat\AnimeClient\API\{FailedResponseException, Kitsu as K};
|
||||
use Aviat\Ion\Event;
|
||||
use Aviat\Ion\Json;
|
||||
use Aviat\Ion\JsonException;
|
||||
|
||||
@ -80,7 +80,7 @@ trait KitsuTrait {
|
||||
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||
{
|
||||
$token = $sessionSegment->get('auth_token');
|
||||
if ( ! $cacheItem->isHit())
|
||||
if ( ! (empty($token) || $cacheItem->isHit()))
|
||||
{
|
||||
$cacheItem->set($token);
|
||||
$cacheItem->save();
|
||||
@ -168,12 +168,20 @@ trait KitsuTrait {
|
||||
}
|
||||
|
||||
$response = $this->getResponse($type, $url, $options);
|
||||
$statusCode = $response->getStatus();
|
||||
|
||||
if ((int) $response->getStatus() > 299 || (int) $response->getStatus() < 200)
|
||||
// Check for requests that are unauthorized
|
||||
if ($statusCode === 401 || $statusCode === 403)
|
||||
{
|
||||
Event::emit(EventType::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Any other type of failed request
|
||||
if ($statusCode > 299 || $statusCode < 200)
|
||||
{
|
||||
if ($logger)
|
||||
{
|
||||
$logger->warning('Non 200 response for api call', (array)$response);
|
||||
$logger->warning('Non 2xx response for api call', (array)$response);
|
||||
}
|
||||
|
||||
throw new FailedResponseException('Failed to get the proper response from the API');
|
||||
@ -188,7 +196,6 @@ trait KitsuTrait {
|
||||
print_r($e);
|
||||
die();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,12 +240,9 @@ trait KitsuTrait {
|
||||
$response = $this->getResponse('POST', ...$args);
|
||||
$validResponseCodes = [200, 201];
|
||||
|
||||
if ( ! \in_array((int) $response->getStatus(), $validResponseCodes, TRUE))
|
||||
if ( ! in_array($response->getStatus(), $validResponseCodes, TRUE) && $logger)
|
||||
{
|
||||
if ($logger)
|
||||
{
|
||||
$logger->warning('Non 201 response for POST api call', $response->getBody());
|
||||
}
|
||||
$logger->warning('Non 201 response for POST api call', $response->getBody());
|
||||
}
|
||||
|
||||
return JSON::decode(wait($response->getBody()->buffer()), TRUE);
|
||||
@ -254,6 +258,6 @@ trait KitsuTrait {
|
||||
protected function deleteRequest(...$args): bool
|
||||
{
|
||||
$response = $this->getResponse('DELETE', ...$args);
|
||||
return ((int) $response->getStatus() === 204);
|
||||
return ($response->getStatus() === 204);
|
||||
}
|
||||
}
|
@ -582,7 +582,7 @@ final class Model {
|
||||
{
|
||||
$defaultOptions = [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => 'anime'
|
||||
],
|
||||
'page' => [
|
||||
@ -608,7 +608,7 @@ final class Model {
|
||||
{
|
||||
$options = [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => 'anime',
|
||||
'status' => $status,
|
||||
],
|
||||
@ -683,7 +683,7 @@ final class Model {
|
||||
$options = [
|
||||
'query' => [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => 'manga',
|
||||
'status' => $status,
|
||||
],
|
||||
@ -811,7 +811,7 @@ final class Model {
|
||||
{
|
||||
$defaultOptions = [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => 'manga'
|
||||
],
|
||||
'page' => [
|
||||
@ -866,7 +866,7 @@ final class Model {
|
||||
*/
|
||||
public function createListItem(array $data): ?Request
|
||||
{
|
||||
$data['user_id'] = $this->getUserIdByUsername($this->getUsername());
|
||||
$data['user_id'] = $this->getUserId();
|
||||
if ($data['id'] === NULL)
|
||||
{
|
||||
return NULL;
|
||||
@ -941,7 +941,7 @@ final class Model {
|
||||
{
|
||||
$options = [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => $type,
|
||||
],
|
||||
'include' => "{$type},{$type}.mappings",
|
||||
@ -1001,7 +1001,7 @@ final class Model {
|
||||
'query' => [
|
||||
'filter' => [
|
||||
'kind' => 'progressed,updated',
|
||||
'userId' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'userId' => $this->getUserId(),
|
||||
],
|
||||
'page' => [
|
||||
'offset' => $offset,
|
||||
@ -1018,6 +1018,18 @@ final class Model {
|
||||
]);
|
||||
}
|
||||
|
||||
private function getUserId(): string
|
||||
{
|
||||
static $userId = NULL;
|
||||
|
||||
if ($userId === NULL)
|
||||
{
|
||||
$userId = $this->getUserIdByUsername($this->getUsername());
|
||||
}
|
||||
|
||||
return $userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the kitsu username from config
|
||||
*
|
||||
@ -1105,7 +1117,7 @@ final class Model {
|
||||
$options = [
|
||||
'query' => [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername(),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => $type,
|
||||
],
|
||||
'page' => [
|
||||
@ -1175,7 +1187,7 @@ final class Model {
|
||||
{
|
||||
$defaultOptions = [
|
||||
'filter' => [
|
||||
'user_id' => $this->getUserIdByUsername($this->getUsername()),
|
||||
'user_id' => $this->getUserId(),
|
||||
'kind' => $type,
|
||||
],
|
||||
'page' => [
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\AnimeClient\Enum\EventType;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
use Aura\Router\Generator;
|
||||
@ -32,6 +33,7 @@ use Aviat\Ion\Di\{
|
||||
Exception\ContainerException,
|
||||
Exception\NotFoundException
|
||||
};
|
||||
use Aviat\Ion\Event;
|
||||
use Aviat\Ion\Exception\DoubleRenderException;
|
||||
use Aviat\Ion\View\{HtmlView, HttpView, JsonView};
|
||||
use InvalidArgumentException;
|
||||
@ -131,6 +133,9 @@ class Controller {
|
||||
'url_type' => 'anime',
|
||||
'urlGenerator' => $urlGenerator,
|
||||
];
|
||||
|
||||
Event::on(EventType::CLEAR_CACHE, fn () => $this->emptyCache());
|
||||
Event::on(EventType::RESET_CACHE_KEY, fn (string $key) => $this->removeCacheItem($key));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -430,5 +435,15 @@ class Controller {
|
||||
(new HttpView($this->container))->redirect($url, $code);
|
||||
exit();
|
||||
}
|
||||
|
||||
private function emptyCache(): void
|
||||
{
|
||||
$this->cache->emptyCache();
|
||||
}
|
||||
|
||||
private function removeCacheItem(string $key): void
|
||||
{
|
||||
$this->cache->deleteItem($key);
|
||||
}
|
||||
}
|
||||
// End of BaseController.php
|
@ -17,6 +17,8 @@
|
||||
namespace Aviat\AnimeClient\Controller;
|
||||
|
||||
use Aviat\AnimeClient\Controller as BaseController;
|
||||
use Aviat\AnimeClient\Enum\EventType;
|
||||
use Aviat\Ion\Event;
|
||||
use Aviat\Ion\View\HtmlView;
|
||||
|
||||
/**
|
||||
@ -30,7 +32,10 @@ final class Misc extends BaseController {
|
||||
*/
|
||||
public function clearCache(): void
|
||||
{
|
||||
$this->cache->clear();
|
||||
$this->checkAuth();
|
||||
|
||||
Event::emit(EventType::CLEAR_CACHE);
|
||||
|
||||
$this->outputHTML('blank', [
|
||||
'title' => 'Cache cleared'
|
||||
]);
|
||||
@ -89,8 +94,6 @@ final class Misc extends BaseController {
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
$this->checkAuth();
|
||||
|
||||
$auth = $this->container->get('auth');
|
||||
$auth->logout();
|
||||
|
||||
|
@ -16,12 +16,14 @@
|
||||
|
||||
namespace Aviat\AnimeClient;
|
||||
|
||||
use Aviat\AnimeClient\Enum\EventType;
|
||||
use function Aviat\Ion\_dir;
|
||||
|
||||
use Aura\Router\{Map, Matcher, Route, Rule};
|
||||
|
||||
use Aviat\AnimeClient\API\FailedResponseException;
|
||||
use Aviat\Ion\Di\ContainerInterface;
|
||||
use Aviat\Ion\Event;
|
||||
use Aviat\Ion\Friend;
|
||||
use Aviat\Ion\Type\StringType;
|
||||
use LogicException;
|
||||
@ -161,10 +163,7 @@ final class Dispatcher extends RoutingBase {
|
||||
throw new LogicException('Missing controller');
|
||||
}
|
||||
|
||||
if (array_key_exists('controller', $route->attributes))
|
||||
{
|
||||
$controllerName = $route->attributes['controller'];
|
||||
}
|
||||
$controllerName = $route->attributes['controller'];
|
||||
|
||||
// Get the full namespace for a controller if a short name is given
|
||||
if (strpos($controllerName, '\\') === FALSE)
|
||||
@ -283,7 +282,7 @@ final class Dispatcher extends RoutingBase {
|
||||
$logger->debug('Dispatcher - controller arguments', $params);
|
||||
}
|
||||
|
||||
\call_user_func_array([$controller, $method], $params);
|
||||
call_user_func_array([$controller, $method], $params);
|
||||
}
|
||||
catch (FailedResponseException $e)
|
||||
{
|
||||
@ -293,7 +292,14 @@ final class Dispatcher extends RoutingBase {
|
||||
'API request timed out',
|
||||
'Failed to retrieve data from API (╯°□°)╯︵ ┻━┻');
|
||||
}
|
||||
|
||||
finally
|
||||
{
|
||||
// Log out on session/api token expiration
|
||||
Event::on(EventType::UNAUTHORIZED, static function () {
|
||||
$controllerName = DEFAULT_CONTROLLER;
|
||||
(new $controllerName($this->container))->logout();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
25
src/AnimeClient/Enum/EventType.php
Normal file
25
src/AnimeClient/Enum/EventType.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\AnimeClient\Enum;
|
||||
|
||||
use Aviat\Ion\Enum as BaseEnum;
|
||||
|
||||
final class EventType extends BaseEnum {
|
||||
public const CLEAR_CACHE = '::clear-cache::';
|
||||
public const RESET_CACHE_KEY = '::reset-cache-key::';
|
||||
public const UNAUTHORIZED = '::unauthorized::';
|
||||
}
|
55
src/Ion/Event.php
Normal file
55
src/Ion/Event.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php declare(strict_types=1);
|
||||
/**
|
||||
* Hummingbird Anime List Client
|
||||
*
|
||||
* An API client for Kitsu to manage anime and manga watch lists
|
||||
*
|
||||
* PHP version 7.4
|
||||
*
|
||||
* @package HummingbirdAnimeClient
|
||||
* @author Timothy J. Warren <tim@timshomepage.net>
|
||||
* @copyright 2015 - 2020 Timothy J. Warren
|
||||
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
||||
* @version 5
|
||||
* @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient
|
||||
*/
|
||||
|
||||
namespace Aviat\Ion;
|
||||
|
||||
/**
|
||||
* A basic event handler
|
||||
*/
|
||||
class Event {
|
||||
private static array $eventMap = [];
|
||||
|
||||
/**
|
||||
* Subscribe to an event
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param callable $handler
|
||||
*/
|
||||
public static function on(string $eventName, callable $handler): void
|
||||
{
|
||||
if ( ! array_key_exists($eventName, static::$eventMap))
|
||||
{
|
||||
static::$eventMap[$eventName] = [];
|
||||
}
|
||||
|
||||
static::$eventMap[$eventName][] = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire off an event
|
||||
*
|
||||
* @param string $eventName
|
||||
* @param array $args
|
||||
*/
|
||||
public static function emit(string $eventName, array $args = []): void
|
||||
{
|
||||
// Call each subscriber with the provided arguments
|
||||
if (array_key_exists($eventName, static::$eventMap))
|
||||
{
|
||||
array_walk(static::$eventMap[$eventName], fn ($fn) => $fn(...$args));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user