All in GraphQL #34

Merged
timw4mail merged 87 commits from develop into master 2020-12-01 10:07:49 -05:00
17 changed files with 274 additions and 279 deletions
Showing only changes of commit 6a82944473 - Show all commits

View File

@ -175,9 +175,10 @@ $routes = [
]
],
'person' => [
'path' => '/people/{id}',
'path' => '/people/{id}{/slug}',
'tokens' => [
'id' => SLUG_PATTERN
'id' => SLUG_PATTERN,
'slug' => SLUG_PATTERN,
]
],
'default_user_info' => [

View File

@ -130,7 +130,7 @@ return static function (array $configArray = []): Container {
return $model;
});
$container->set('anilist-model', static function(ContainerInterface $container): Anilist\Model {
$requestBuilder = new AnilistRequestBuilder();
$requestBuilder = new AnilistRequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request'));
$listItem = new Anilist\ListItem();

View File

@ -174,7 +174,7 @@ use function Aviat\AnimeClient\getLocalImg;
<section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $pid => $person): ?>
<article class='character small-person'>
<?php $link = $url->generate('person', ['id' => $person['id']]) ?>
<?php $link = $url->generate('person', ['id' => $person['id'], 'slug' => $person['slug']]) ?>
<div class="name">
<a href="<?= $link ?>">
<?= $person['name'] ?>

View File

@ -100,7 +100,7 @@
<section class='content media-wrap flex flex-wrap flex-justify-start'>
<?php foreach ($people as $person): ?>
<article class='character person'>
<?php $link = $url->generate('person', ['id' => $person['id']]) ?>
<?php $link = $url->generate('person', ['id' => $person['id'], 'slug' => $person['slug']]) ?>
<div class="name">
<a href="<?= $link ?>">
<?= $person['name'] ?>

View File

@ -16,17 +16,28 @@
namespace Aviat\AnimeClient\API\Anilist;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Aviat\AnimeClient\API\Anilist;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json;
use Aviat\Ion\JsonException;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use const Aviat\AnimeClient\USER_AGENT;
use Aviat\AnimeClient\API\APIRequestBuilder;
final class AnilistRequestBuilder extends APIRequestBuilder {
use ContainerAware;
/**
* The base url for api requests
* @var string $base_url
*/
protected string $baseUrl = 'https://graphql.anilist.co';
protected string $baseUrl = Anilist::BASE_URL;
/**
* Valid HTTP request methods
@ -40,8 +51,226 @@ final class AnilistRequestBuilder extends APIRequestBuilder {
* @var array
*/
protected array $defaultHeaders = [
'User-Agent' => USER_AGENT,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
// 'Accept-Encoding' => 'gzip',
'Content-type' => 'application/json',
'User-Agent' => USER_AGENT,
];
public function __construct(ContainerInterface $container)
{
$this->setContainer($container);
}
/**
* Create a request object
* @param string $url
* @param array $options
* @return Request
* @throws Throwable
*/
public function setUpRequest(string $url, array $options = []): Request
{
$config = $this->getContainer()->get('config');
$anilistConfig = $config->get('anilist');
$request = $this->newRequest('POST', $url);
// You can only authenticate the request if you
// actually have an access_token saved
if ($config->has(['anilist', 'access_token']))
{
$request = $request->setAuth('bearer', $anilistConfig['access_token']);
}
if (array_key_exists('form_params', $options))
{
$request = $request->setFormFields($options['form_params']);
}
if (array_key_exists('query', $options))
{
$request = $request->setQuery($options['query']);
}
if (array_key_exists('body', $options))
{
$request = $request->setJsonBody($options['body']);
}
if (array_key_exists('headers', $options))
{
$request = $request->setHeaders($options['headers']);
}
return $request->getFullRequest();
}
/**
* Run a GraphQL API query
*
* @param string $name
* @param array $variables
* @return array
*/
public function runQuery(string $name, array $variables = []): array
{
$file = realpath(__DIR__ . "/Queries/{$name}.graphql");
if ( ! file_exists($file))
{
throw new LogicException('GraphQL query file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if ( ! empty($variables))
{
$body['variables'] = [];
foreach($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->postRequest([
'body' => $body
]);
}
/**
* @param string $name
* @param array $variables
* @return Request
* @throws Throwable
*/
public function mutateRequest (string $name, array $variables = []): Request
{
$file = realpath(__DIR__ . "/Mutations/{$name}.graphql");
if (!file_exists($file))
{
throw new LogicException('GraphQL mutation file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if (!empty($variables)) {
$body['variables'] = [];
foreach ($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest(Anilist::BASE_URL, [
'body' => $body,
]);
}
/**
* @param string $name
* @param array $variables
* @return array
* @throws Throwable
*/
public function mutate (string $name, array $variables = []): array
{
$request = $this->mutateRequest($name, $variables);
$response = $this->getResponseFromRequest($request);
return Json::decode(wait($response->getBody()->buffer()));
}
/**
* Make a request
*
* @param string $url
* @param array $options
* @return Response
* @throws Throwable
*/
private function getResponse(string $url, array $options = []): Response
{
$logger = $this->container->getLogger('anilist-request');
$request = $this->setUpRequest($url, $options);
$response = getResponse($request);
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* @param Request $request
* @return Response
* @throws Throwable
*/
private function getResponseFromRequest(Request $request): Response
{
$logger = $this->container->getLogger('anilist-request');
$response = getResponse($request);
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* Remove some boilerplate for post requests
*
* @param array $options
* @return array
* @throws Throwable
*/
protected function postRequest(array $options = []): array
{
$response = $this->getResponse(Anilist::BASE_URL, $options);
$validResponseCodes = [200, 201];
$logger = $this->container->getLogger('anilist-request');
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
//'requestHeaders' => $request->getHeaders(),
]);
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE))
{
$logger->warning('Non 200 response for POST api call', (array)$response->getBody());
}
$rawBody = wait($response->getBody()->buffer());
try
{
return Json::decode($rawBody);
}
catch (JsonException $e)
{
dump($e);
dump($rawBody);
die();
}
}
}

View File

@ -16,21 +16,8 @@
namespace Aviat\AnimeClient\API\Anilist;
use const Aviat\AnimeClient\USER_AGENT;
use function Amp\Promise\wait;
use function Aviat\AnimeClient\getResponse;
use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Aviat\AnimeClient\API\Anilist;
use Aviat\Ion\Json;
use Aviat\Ion\Di\ContainerAware;
use LogicException;
use Throwable;
trait AnilistTrait {
use ContainerAware;
@ -40,24 +27,6 @@ trait AnilistTrait {
*/
protected AnilistRequestBuilder $requestBuilder;
/**
* The base url for api requests
* @var string $base_url
*/
protected string $baseUrl = Anilist::BASE_URL;
/**
* HTTP headers to send with every request
*
* @var array
*/
protected array $defaultHeaders = [
'Accept' => 'application/json',
'Accept-Encoding' => 'gzip',
'Content-type' => 'application/json',
'User-Agent' => USER_AGENT,
];
/**
* Set the request builder object
*
@ -69,223 +38,4 @@ trait AnilistTrait {
$this->requestBuilder = $requestBuilder;
return $this;
}
/**
* Create a request object
* @param string $url
* @param array $options
* @return Request
* @throws Throwable
*/
public function setUpRequest(string $url, array $options = []): Request
{
$config = $this->getContainer()->get('config');
$anilistConfig = $config->get('anilist');
$request = $this->requestBuilder->newRequest('POST', $url);
// You can only authenticate the request if you
// actually have an access_token saved
if ($config->has(['anilist', 'access_token']))
{
$request = $request->setAuth('bearer', $anilistConfig['access_token']);
}
if (array_key_exists('form_params', $options))
{
$request = $request->setFormFields($options['form_params']);
}
if (array_key_exists('query', $options))
{
$request = $request->setQuery($options['query']);
}
if (array_key_exists('body', $options))
{
$request = $request->setJsonBody($options['body']);
}
if (array_key_exists('headers', $options))
{
$request = $request->setHeaders($options['headers']);
}
return $request->getFullRequest();
}
/**
* Run a GraphQL API query
*
* @param string $name
* @param array $variables
* @return array
*/
public function runQuery(string $name, array $variables = []): array
{
$file = realpath(__DIR__ . "/Queries/{$name}.graphql");
if ( ! file_exists($file))
{
throw new LogicException('GraphQL query file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if ( ! empty($variables))
{
$body['variables'] = [];
foreach($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->postRequest([
'body' => $body
]);
}
/**
* @param string $name
* @param array $variables
* @return Request
* @throws Throwable
*/
public function mutateRequest (string $name, array $variables = []): Request
{
$file = realpath(__DIR__ . "/Mutations/{$name}.graphql");
if (!file_exists($file))
{
throw new LogicException('GraphQL mutation file does not exist.');
}
$query = file_get_contents($file);
$body = [
'query' => $query
];
if (!empty($variables)) {
$body['variables'] = [];
foreach ($variables as $key => $val)
{
$body['variables'][$key] = $val;
}
}
return $this->setUpRequest(Anilist::BASE_URL, [
'body' => $body,
]);
}
/**
* @param string $name
* @param array $variables
* @return array
* @throws Throwable
*/
public function mutate (string $name, array $variables = []): array
{
$request = $this->mutateRequest($name, $variables);
$response = $this->getResponseFromRequest($request);
return Json::decode(wait($response->getBody()->buffer()));
}
/**
* Make a request
*
* @param string $url
* @param array $options
* @return Response
* @throws Throwable
*/
private function getResponse(string $url, array $options = []): Response
{
$logger = NULL;
if ($this->getContainer())
{
$logger = $this->container->getLogger('anilist-request');
}
$request = $this->setUpRequest($url, $options);
$response = getResponse($request);
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* @param Request $request
* @return Response
* @throws Throwable
*/
private function getResponseFromRequest(Request $request): Response
{
$logger = NULL;
if ($this->getContainer())
{
$logger = $this->container->getLogger('anilist-request');
}
$response = getResponse($request);
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
'requestHeaders' => $request->getHeaders(),
]);
return $response;
}
/**
* Remove some boilerplate for post requests
*
* @param array $options
* @return array
* @throws Throwable
*/
protected function postRequest(array $options = []): array
{
$response = $this->getResponse(Anilist::BASE_URL, $options);
$validResponseCodes = [200, 201];
$logger = NULL;
if ($this->getContainer())
{
$logger = $this->container->getLogger('anilist-request');
$logger->debug('Anilist response', [
'status' => $response->getStatus(),
'reason' => $response->getReason(),
'body' => $response->getBody(),
'headers' => $response->getHeaders(),
//'requestHeaders' => $request->getHeaders(),
]);
}
if ( ! \in_array($response->getStatus(), $validResponseCodes, TRUE))
{
if ($logger !== NULL)
{
$logger->warning('Non 200 response for POST api call', (array)$response->getBody());
}
}
// dump(wait($response->getBody()->buffer()));
return Json::decode(wait($response->getBody()->buffer()));
}
}

View File

@ -38,7 +38,7 @@ final class ListItem extends AbstractListItem {
public function create(array $data): Request
{
$checkedData = Types\MediaListEntry::check($data);
return $this->mutateRequest('CreateMediaListEntry', $checkedData);
return $this->requestBuilder->mutateRequest('CreateMediaListEntry', $checkedData);
}
/**
@ -50,7 +50,7 @@ final class ListItem extends AbstractListItem {
public function createFull(array $data): Request
{
$checkedData = Types\MediaListEntry::check($data);
return $this->mutateRequest('CreateFullMediaListEntry', $checkedData);
return $this->requestBuilder->mutateRequest('CreateFullMediaListEntry', $checkedData);
}
/**
@ -62,7 +62,7 @@ final class ListItem extends AbstractListItem {
*/
public function delete(string $id, string $type = 'anime'): Request
{
return $this->mutateRequest('DeleteMediaListEntry', ['id' => $id]);
return $this->requestBuilder->mutateRequest('DeleteMediaListEntry', ['id' => $id]);
}
/**
@ -73,7 +73,7 @@ final class ListItem extends AbstractListItem {
*/
public function get(string $id): array
{
return $this->runQuery('MediaListItem', ['id' => $id]);
return $this->requestBuilder->runQuery('MediaListItem', ['id' => $id]);
}
/**
@ -90,7 +90,7 @@ final class ListItem extends AbstractListItem {
'progress' => $data->progress,
]);
return $this->mutateRequest('IncrementMediaListEntry', $checkedData);
return $this->requestBuilder->mutateRequest('IncrementMediaListEntry', $checkedData);
}
/**
@ -120,6 +120,6 @@ final class ListItem extends AbstractListItem {
'notes' => $notes,
]);
return $this->mutateRequest('UpdateMediaListEntry', $updateData);
return $this->requestBuilder->mutateRequest('UpdateMediaListEntry', $updateData);
}
}

View File

@ -89,7 +89,7 @@ final class Model
*/
public function checkAuth(): array
{
return $this->runQuery('CheckLogin');
return $this->requestBuilder->runQuery('CheckLogin');
}
/**
@ -110,7 +110,7 @@ final class Model
throw new InvalidArgumentException('Anilist username is not defined in config');
}
return $this->runQuery('SyncUserList', [
return $this->requestBuilder->runQuery('SyncUserList', [
'name' => $anilistUser,
'type' => $type,
]);
@ -275,17 +275,25 @@ final class Model
* Get the Anilist list item id from the media id from its MAL id
* this way is more accurate than getting the list item id
* directly from the MAL id
*
* @param string $mediaId
* @return string|null
*/
private function getListIdFromMediaId(string $mediaId): string
private function getListIdFromMediaId(string $mediaId): ?string
{
$config = $this->container->get('config');
$anilistUser = $config->get(['anilist', 'username']);
$info = $this->runQuery('ListItemIdByMediaId', [
$info = $this->requestBuilder->runQuery('ListItemIdByMediaId', [
'id' => $mediaId,
'userName' => $anilistUser,
]);
if ( ! empty($info['errors']))
{
return NULL;
}
return (string)$info['data']['MediaList']['id'];
}
@ -303,7 +311,7 @@ final class Model
return NULL;
}
$info = $this->runQuery('MediaIdByMalId', [
$info = $this->requestBuilder->runQuery('MediaIdByMalId', [
'id' => $malId,
'type' => mb_strtoupper($type),
]);

View File

@ -388,6 +388,8 @@ final class KitsuRequestBuilder extends APIRequestBuilder {
Event::emit(EventType::UNAUTHORIZED);
}
$rawBody = wait($response->getBody()->buffer());
// Any other type of failed request
if ($statusCode > 299 || $statusCode < 200)
{

View File

@ -82,7 +82,7 @@ query ($slug: String!) {
canonical
localized
}
#slug
slug
}
role
}

View File

@ -82,7 +82,7 @@ query ($id: ID!) {
canonical
localized
}
#slug
slug
}
role
}

View File

@ -93,7 +93,7 @@ query ($slug: String!) {
canonical
localized
}
#slug
slug
}
role
}

View File

@ -93,7 +93,7 @@ query ($id: ID!) {
canonical
localized
}
#slug
slug
}
role
}

View File

@ -94,6 +94,7 @@ final class AnimeTransformer extends AbstractTransformer {
'image' => [
'original' => $person['image']['original']['url'],
],
'slug' => $person['slug'],
];
usort($staff[$role], fn ($a, $b) => $a['name'] <=> $b['name']);

View File

@ -87,6 +87,7 @@ final class MangaTransformer extends AbstractTransformer {
$staff[$role][$person['id']] = [
'id' => $person['id'],
'slug' => $person['slug'],
'name' => $name,
'image' => [
'original' => $person['image']['original']['url'],

View File

@ -147,13 +147,13 @@ abstract class BaseCommand extends Command {
// -------------------------------------------------------------------------
$app_logger = new Logger('animeclient');
$app_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', Logger::NOTICE));
$app_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/app-cli.log', Logger::WARNING));
$kitsu_request_logger = new Logger('kitsu-request');
$kitsu_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/kitsu_request-cli.log', Logger::NOTICE));
$kitsu_request_logger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/kitsu_request-cli.log', Logger::WARNING));
$anilistRequestLogger = new Logger('anilist-request');
$anilistRequestLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/anilist_request-cli.log', Logger::NOTICE));
$anilistRequestLogger->pushHandler(new RotatingFileHandler($APP_DIR . '/logs/anilist_request-cli.log', Logger::WARNING));
$container->setLogger($app_logger);
$container->setLogger($anilistRequestLogger, 'anilist-request');
@ -203,7 +203,7 @@ abstract class BaseCommand extends Command {
return $model;
});
$container->set('anilist-model', static function ($container): Anilist\Model {
$requestBuilder = new Anilist\AnilistRequestBuilder();
$requestBuilder = new Anilist\AnilistRequestBuilder($container);
$requestBuilder->setLogger($container->getLogger('anilist-request'));
$listItem = new Anilist\ListItem();

View File

@ -51,11 +51,14 @@ final class People extends BaseController {
* Show information about a person
*
* @param string $id
* @param string|null $slug
* @return void
* @throws ContainerException
* @throws NotFoundException
*/
public function index(string $id): void
public function index(string $id, ?string $slug = NULL): void
{
$rawData = $this->model->getPerson($id);
$rawData = $this->model->getPerson($id, $slug);
$data = (new PersonTransformer())->transform($rawData)->toArray();
if (( ! array_key_exists('data', $rawData)) || empty($rawData['data']))