Make authentication more reliable for list syncing
This commit is contained in:
parent
7373cf93b7
commit
ee18d407a2
@ -21,7 +21,6 @@ use const Aviat\AnimeClient\USER_AGENT;
|
|||||||
use function Amp\Promise\wait;
|
use function Amp\Promise\wait;
|
||||||
use function Aviat\AnimeClient\getResponse;
|
use function Aviat\AnimeClient\getResponse;
|
||||||
|
|
||||||
use Amp;
|
|
||||||
use Amp\Http\Client\Request;
|
use Amp\Http\Client\Request;
|
||||||
use Amp\Http\Client\Body\FormBody;
|
use Amp\Http\Client\Body\FormBody;
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
namespace Aviat\AnimeClient\API\Anilist\Transformer;
|
namespace Aviat\AnimeClient\API\Anilist\Transformer;
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus;
|
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Anilist as AnilistStatus;
|
||||||
|
use Aviat\AnimeClient\API\Enum\MangaReadingStatus\Kitsu as KitsuStatus;
|
||||||
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
use Aviat\AnimeClient\API\Mapping\MangaReadingStatus;
|
||||||
use Aviat\AnimeClient\Types\MangaListItem;
|
use Aviat\AnimeClient\Types\MangaListItem;
|
||||||
use Aviat\AnimeClient\Types\FormItem;
|
use Aviat\AnimeClient\Types\FormItem;
|
||||||
@ -40,6 +41,8 @@ class MangaListTransformer extends AbstractTransformer {
|
|||||||
*/
|
*/
|
||||||
public function untransform(array $item): FormItem
|
public function untransform(array $item): FormItem
|
||||||
{
|
{
|
||||||
|
$reconsuming = $item['status'] === AnilistStatus::REPEATING;
|
||||||
|
|
||||||
return FormItem::from([
|
return FormItem::from([
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
'mal_id' => $item['media']['idMal'],
|
'mal_id' => $item['media']['idMal'],
|
||||||
@ -49,8 +52,10 @@ class MangaListTransformer extends AbstractTransformer {
|
|||||||
'progress' => $item['progress'],
|
'progress' => $item['progress'],
|
||||||
'rating' => $item['score'],
|
'rating' => $item['score'],
|
||||||
'reconsumeCount' => $item['repeat'],
|
'reconsumeCount' => $item['repeat'],
|
||||||
'reconsuming' => $item['status'] === AnilistStatus::REPEATING,
|
'reconsuming' => $reconsuming,
|
||||||
'status' => MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
|
'status' => $reconsuming
|
||||||
|
? KitsuStatus::READING
|
||||||
|
: MangaReadingStatus::ANILIST_TO_KITSU[$item['status']],
|
||||||
'updatedAt' => (new DateTime())
|
'updatedAt' => (new DateTime())
|
||||||
->setTimestamp($item['updatedAt'])
|
->setTimestamp($item['updatedAt'])
|
||||||
->format(DateTime::W3C),
|
->format(DateTime::W3C),
|
||||||
|
@ -29,6 +29,7 @@ use Aviat\Ion\Di\{ContainerAware, ContainerInterface};
|
|||||||
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
use Aviat\Ion\Di\Exception\{ContainerException, NotFoundException};
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
use const PHP_SAPI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kitsu API Authentication
|
* Kitsu API Authentication
|
||||||
@ -83,35 +84,67 @@ final class Auth {
|
|||||||
|
|
||||||
$auth = $this->model->authenticate($username, $password);
|
$auth = $this->model->authenticate($username, $password);
|
||||||
|
|
||||||
if (FALSE !== $auth)
|
return $this->storeAuth($auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the current user is authenticated
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function isAuthenticated(): bool
|
||||||
{
|
{
|
||||||
// Set the token in the cache for command line operations
|
return ($this->getAuthToken() !== NULL);
|
||||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
|
|
||||||
$cacheItem->set($auth['access_token']);
|
|
||||||
$cacheItem->save();
|
|
||||||
|
|
||||||
// Set the token expiration in the cache
|
|
||||||
$expireTime = $auth['created_at'] + $auth['expires_in'];
|
|
||||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_EXP_CACHE_KEY);
|
|
||||||
$cacheItem->set($expireTime);
|
|
||||||
$cacheItem->save();
|
|
||||||
|
|
||||||
// Set the refresh token in the cache
|
|
||||||
$cacheItem = $this->cache->getItem(K::AUTH_TOKEN_REFRESH_CACHE_KEY);
|
|
||||||
$cacheItem->set($auth['refresh_token']);
|
|
||||||
$cacheItem->save();
|
|
||||||
|
|
||||||
// Set the session values
|
|
||||||
$this->segment->set('auth_token', $auth['access_token']);
|
|
||||||
$this->segment->set('auth_token_expires', $expireTime);
|
|
||||||
$this->segment->set('refresh_token', $auth['refresh_token']);
|
|
||||||
|
|
||||||
return TRUE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return FALSE;
|
/**
|
||||||
|
* Clear authentication values
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function logout(): void
|
||||||
|
{
|
||||||
|
$this->segment->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the authentication token from the session
|
||||||
|
*
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
private function getAuthToken(): ?string
|
||||||
|
{
|
||||||
|
$now = time();
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli')
|
||||||
|
{
|
||||||
|
$token = $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL);
|
||||||
|
$refreshToken = $this->cacheGet(K::AUTH_TOKEN_REFRESH_CACHE_KEY, NULL);
|
||||||
|
$expireTime = $this->cacheGet(K::AUTH_TOKEN_EXP_CACHE_KEY);
|
||||||
|
$isExpired = $now > $expireTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$token = $this->segment->get('auth_token', NULL);
|
||||||
|
$refreshToken = $this->segment->get('refresh_token', NULL);
|
||||||
|
$isExpired = $now > $this->segment->get('auth_token_expires', $now + 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to re-authenticate with refresh token
|
||||||
|
/* if ($isExpired === TRUE && $refreshToken !== NULL)
|
||||||
|
{
|
||||||
|
if ($this->reAuthenticate($refreshToken) !== NULL)
|
||||||
|
{
|
||||||
|
return (PHP_SAPI === 'cli')
|
||||||
|
? $this->cacheGet(K::AUTH_TOKEN_CACHE_KEY, NULL)
|
||||||
|
: $this->segment->get('auth_token', NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return $token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make the call to re-authenticate with the existing refresh token
|
* Make the call to re-authenticate with the existing refresh token
|
||||||
@ -121,10 +154,15 @@ final class Auth {
|
|||||||
* @throws InvalidArgumentException
|
* @throws InvalidArgumentException
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function reAuthenticate(string $token): bool
|
private function reAuthenticate(string $token): bool
|
||||||
{
|
{
|
||||||
$auth = $this->model->reAuthenticate($token);
|
$auth = $this->model->reAuthenticate($token);
|
||||||
|
|
||||||
|
return $this->storeAuth($auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function storeAuth($auth): bool
|
||||||
|
{
|
||||||
if (FALSE !== $auth)
|
if (FALSE !== $auth)
|
||||||
{
|
{
|
||||||
// Set the token in the cache for command line operations
|
// Set the token in the cache for command line operations
|
||||||
@ -153,52 +191,15 @@ final class Auth {
|
|||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function cacheGet(string $key, $default = NULL)
|
||||||
/**
|
|
||||||
* Check whether the current user is authenticated
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
public function isAuthenticated(): bool
|
|
||||||
{
|
{
|
||||||
return ($this->get_auth_token() !== FALSE);
|
$cacheItem = $this->cache->getItem($key);
|
||||||
|
if ( ! $cacheItem->isHit())
|
||||||
|
{
|
||||||
|
return $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return $cacheItem->get();
|
||||||
* Clear authentication values
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function logout(): void
|
|
||||||
{
|
|
||||||
$this->segment->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the authentication token from the session
|
|
||||||
*
|
|
||||||
* @return string|false
|
|
||||||
*/
|
|
||||||
public function get_auth_token()
|
|
||||||
{
|
|
||||||
$now = time();
|
|
||||||
|
|
||||||
$token = $this->segment->get('auth_token', FALSE);
|
|
||||||
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
|
||||||
$isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000);
|
|
||||||
|
|
||||||
// Attempt to re-authenticate with refresh token
|
|
||||||
/* if ($isExpired && $refreshToken)
|
|
||||||
{
|
|
||||||
if ($this->reAuthenticate($refreshToken))
|
|
||||||
{
|
|
||||||
return $this->segment->get('auth_token', FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
} */
|
|
||||||
|
|
||||||
return $token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// End of KitsuAuth.php
|
// End of KitsuAuth.php
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Kitsu;
|
namespace Aviat\AnimeClient\API\Kitsu;
|
||||||
|
|
||||||
|
use const PHP_SAPI;
|
||||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
||||||
|
|
||||||
use function Amp\Promise\wait;
|
use function Amp\Promise\wait;
|
||||||
@ -69,11 +70,14 @@ trait KitsuTrait {
|
|||||||
->getSegment(SESSION_SEGMENT);
|
->getSegment(SESSION_SEGMENT);
|
||||||
|
|
||||||
$cache = $this->getContainer()->get('cache');
|
$cache = $this->getContainer()->get('cache');
|
||||||
$cacheItem = $cache->getItem('kitsu-auth-token');
|
$cacheItem = $cache->getItem(K::AUTH_TOKEN_CACHE_KEY);
|
||||||
$token = null;
|
$token = null;
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli' && $cacheItem->isHit())
|
||||||
if ($sessionSegment->get('auth_token') !== NULL && $url !== K::AUTH_URL)
|
{
|
||||||
|
$token = $cacheItem->get();
|
||||||
|
}
|
||||||
|
else if ($url !== K::AUTH_URL && $sessionSegment->get('auth_token') !== NULL)
|
||||||
{
|
{
|
||||||
$token = $sessionSegment->get('auth_token');
|
$token = $sessionSegment->get('auth_token');
|
||||||
if ( ! $cacheItem->isHit())
|
if ( ! $cacheItem->isHit())
|
||||||
@ -82,12 +86,8 @@ trait KitsuTrait {
|
|||||||
$cacheItem->save();
|
$cacheItem->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ($sessionSegment->get('auth_token') === NULL && $cacheItem->isHit())
|
|
||||||
{
|
|
||||||
$token = $cacheItem->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (NULL !== $token)
|
if ($token !== NULL)
|
||||||
{
|
{
|
||||||
$request = $request->setAuth('bearer', $token);
|
$request = $request->setAuth('bearer', $token);
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,8 @@ use Aviat\AnimeClient\API\{Anilist, CacheTrait, Kitsu};
|
|||||||
use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder;
|
use Aviat\AnimeClient\API\Kitsu\KitsuRequestBuilder;
|
||||||
use Aviat\Banker\Pool;
|
use Aviat\Banker\Pool;
|
||||||
use Aviat\Ion\Config;
|
use Aviat\Ion\Config;
|
||||||
use Aviat\Ion\Di\{Container, ContainerAware};
|
use Aviat\Ion\Di\{Container, ContainerInterface, ContainerAware};
|
||||||
use ConsoleKit\{Command, ConsoleException};
|
use ConsoleKit\{Colors, Command, ConsoleException};
|
||||||
use ConsoleKit\Widgets\Box;
|
use ConsoleKit\Widgets\Box;
|
||||||
use Laminas\Diactoros\{Response, ServerRequestFactory};
|
use Laminas\Diactoros\{Response, ServerRequestFactory};
|
||||||
use Monolog\Handler\RotatingFileHandler;
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
@ -44,15 +44,30 @@ abstract class BaseCommand extends Command {
|
|||||||
* Echo text in a box
|
* Echo text in a box
|
||||||
*
|
*
|
||||||
* @param string $message
|
* @param string $message
|
||||||
|
* @param string|int|null $fgColor
|
||||||
|
* @param string|int|null $bgColor
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
protected function echoBox($message): void
|
public function echoBox(string $message, $fgColor = NULL, $bgColor = NULL): void
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
echo "\n";
|
$len = strlen($message);
|
||||||
|
|
||||||
|
// color message
|
||||||
|
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||||
|
$colorLen = strlen($message);
|
||||||
|
|
||||||
|
// create the box
|
||||||
$box = new Box($this->getConsole(), $message);
|
$box = new Box($this->getConsole(), $message);
|
||||||
|
|
||||||
|
if ($len !== $colorLen)
|
||||||
|
{
|
||||||
|
$box->setPadding((($colorLen - $len) / 2) + 2);
|
||||||
|
}
|
||||||
|
|
||||||
$box->write();
|
$box->write();
|
||||||
|
|
||||||
echo "\n";
|
echo "\n";
|
||||||
}
|
}
|
||||||
catch (ConsoleException $e)
|
catch (ConsoleException $e)
|
||||||
@ -61,12 +76,42 @@ abstract class BaseCommand extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function echo(string $message): void
|
||||||
|
{
|
||||||
|
$this->_line($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function echoSuccess(string $message): void
|
||||||
|
{
|
||||||
|
$this->_line($message, Colors::GREEN | Colors::BOLD, Colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function echoWarning(string $message): void
|
||||||
|
{
|
||||||
|
$this->_line($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function echoWarningBox(string $message): void
|
||||||
|
{
|
||||||
|
$this->echoBox($message, Colors::YELLOW | Colors::BOLD, Colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function echoError(string $message): void
|
||||||
|
{
|
||||||
|
$this->_line($message, Colors::RED | Colors::BOLD, Colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function echoErrorBox(string $message): void
|
||||||
|
{
|
||||||
|
$this->echoBox($message, Colors::RED | Colors::BOLD, Colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup the Di container
|
* Setup the Di container
|
||||||
*
|
*
|
||||||
* @return Container
|
* @return Containerinterface
|
||||||
*/
|
*/
|
||||||
protected function setupContainer(): Container
|
public function setupContainer(): ContainerInterface
|
||||||
{
|
{
|
||||||
$APP_DIR = realpath(__DIR__ . '/../../../app');
|
$APP_DIR = realpath(__DIR__ . '/../../../app');
|
||||||
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
||||||
@ -82,7 +127,7 @@ abstract class BaseCommand extends Command {
|
|||||||
|
|
||||||
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
||||||
|
|
||||||
$di = static function ($configArray) use ($APP_DIR): Container {
|
$di = static function (array $configArray) use ($APP_DIR): Container {
|
||||||
$container = new Container();
|
$container = new Container();
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -103,9 +148,7 @@ abstract class BaseCommand extends Command {
|
|||||||
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
||||||
|
|
||||||
// Create Config Object
|
// Create Config Object
|
||||||
$container->set('config', static function() use ($configArray): Config {
|
$container->set('config', fn () => new Config($configArray));
|
||||||
return new Config($configArray);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Cache Object
|
// Create Cache Object
|
||||||
$container->set('cache', static function($container) {
|
$container->set('cache', static function($container) {
|
||||||
@ -115,28 +158,20 @@ abstract class BaseCommand extends Command {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create Aura Router Object
|
// Create Aura Router Object
|
||||||
$container->set('aura-router', static function() {
|
$container->set('aura-router', fn () => new RouterContainer);
|
||||||
return new RouterContainer;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create Request/Response Objects
|
// Create Request/Response Objects
|
||||||
$container->set('request', static function() {
|
$container->set('request', fn () => ServerRequestFactory::fromGlobals(
|
||||||
return ServerRequestFactory::fromGlobals(
|
|
||||||
$_SERVER,
|
$_SERVER,
|
||||||
$_GET,
|
$_GET,
|
||||||
$_POST,
|
$_POST,
|
||||||
$_COOKIE,
|
$_COOKIE,
|
||||||
$_FILES
|
$_FILES
|
||||||
);
|
));
|
||||||
});
|
$container->set('response', fn () => new Response);
|
||||||
$container->set('response', static function(): Response {
|
|
||||||
return new Response;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create session Object
|
// Create session Object
|
||||||
$container->set('session', static function() {
|
$container->set('session', fn () => (new SessionFactory())->newInstance($_COOKIE));
|
||||||
return (new SessionFactory())->newInstance($_COOKIE);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
$container->set('kitsu-model', static function($container): Kitsu\Model {
|
$container->set('kitsu-model', static function($container): Kitsu\Model {
|
||||||
@ -175,21 +210,21 @@ abstract class BaseCommand extends Command {
|
|||||||
return $model;
|
return $model;
|
||||||
});
|
});
|
||||||
|
|
||||||
$container->set('auth', static function($container): Kitsu\Auth {
|
$container->set('auth', fn ($container) => new Kitsu\Auth($container));
|
||||||
return new Kitsu\Auth($container);
|
|
||||||
});
|
|
||||||
|
|
||||||
$container->set('url-generator', static function($container): UrlGenerator {
|
$container->set('url-generator', fn ($container) => new UrlGenerator($container));
|
||||||
return new UrlGenerator($container);
|
|
||||||
});
|
|
||||||
|
|
||||||
$container->set('util', static function($container): Util {
|
$container->set('util', fn ($container) => new Util($container));
|
||||||
return new Util($container);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $container;
|
return $container;
|
||||||
};
|
};
|
||||||
|
|
||||||
return $di($configArray);
|
return $di($configArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function _line(string $message, $fgColor = NULL, $bgColor = NULL): void
|
||||||
|
{
|
||||||
|
$message = Colors::colorize($message, $fgColor, $bgColor);
|
||||||
|
$this->getConsole()->writeln($message);
|
||||||
|
}
|
||||||
}
|
}
|
28
src/AnimeClient/Enum/ListType.php
Normal file
28
src/AnimeClient/Enum/ListType.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of lists
|
||||||
|
*/
|
||||||
|
final class ListType extends BaseEnum {
|
||||||
|
public const ANIME = 'anime';
|
||||||
|
public const DRAMA = 'drama';
|
||||||
|
public const MANGA = 'manga';
|
||||||
|
}
|
28
src/AnimeClient/Enum/SyncAction.php
Normal file
28
src/AnimeClient/Enum/SyncAction.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types of actions when syncing lists from different APIs
|
||||||
|
*/
|
||||||
|
final class SyncAction extends BaseEnum {
|
||||||
|
public const CREATE = 'create';
|
||||||
|
public const UPDATE = 'update';
|
||||||
|
public const DELETE = 'delete';
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user