Set up Event-based handling for a few things
timw4mail/HummingBirdAnimeClient/pipeline/head This commit looks good Details

This commit is contained in:
Timothy Warren 2020-05-06 13:16:40 -04:00
parent dc20d8de7c
commit 05c50387f6
8 changed files with 183 additions and 46 deletions

View File

@ -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)
{

View File

@ -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);
}
}

View File

@ -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' => [

View File

@ -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

View File

@ -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();

View File

@ -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();
});
}
}
/**

View 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
View 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));
}
}
}