From b861db5d1fd2a5e5d4cd834ac2dc7e4fd3e58920 Mon Sep 17 00:00:00 2001 From: "Timothy J. Warren" Date: Wed, 5 Apr 2017 13:01:51 -0400 Subject: [PATCH] Catch API timeouts --- src/API/JsonAPI.php | 15 ++-- src/API/Kitsu/KitsuTrait.php | 6 +- src/API/Kitsu/Model.php | 9 +- src/Controller/Character.php | 158 +++++++++++++++++++++++++++-------- src/Dispatcher.php | 22 +++-- 5 files changed, 159 insertions(+), 51 deletions(-) diff --git a/src/API/JsonAPI.php b/src/API/JsonAPI.php index b23eb090..210afc5a 100644 --- a/src/API/JsonAPI.php +++ b/src/API/JsonAPI.php @@ -54,7 +54,9 @@ class JsonAPI { ]; // Reorganize included data - $included = static::organizeIncluded($data['included']); + $included = (array_key_exists('included', $data)) + ? static::organizeIncluded($data['included']) + : []; // Inline organized data foreach($data['data'] as $i => &$item) @@ -138,10 +140,7 @@ class JsonAPI { continue; } - $relationship[$typeKey][$idKey] = array_merge( - $included[$typeKey][$idKey], - $relationship[$typeKey][$idKey] ?? [] - ); + $relationship[$typeKey][$idKey][$j] = $included[$typeKey][$idKey]; } } } @@ -149,6 +148,8 @@ class JsonAPI { } } + $data['data']['included'] = $included; + return $data['data']; } @@ -202,11 +203,11 @@ class JsonAPI { { foreach($items as $id => $item) { - if (array_key_exists('relationships', $item)) + if (array_key_exists('relationships', $item) && is_array($item['relationships'])) { foreach($item['relationships'] as $relType => $props) { - if (array_key_exists('data', $props)) + if (array_key_exists('data', $props) && is_array($props['data']) && array_key_exists('id', $props['data'])) { if (array_key_exists($props['data']['id'], $organized[$props['data']['type']])) { diff --git a/src/API/Kitsu/KitsuTrait.php b/src/API/Kitsu/KitsuTrait.php index 68c8b91c..fe2167b7 100644 --- a/src/API/Kitsu/KitsuTrait.php +++ b/src/API/Kitsu/KitsuTrait.php @@ -22,7 +22,7 @@ use function Amp\wait; use Amp\Artax\{Client, Request}; use Aviat\AnimeClient\AnimeClient; -use Aviat\AnimeClient\API\Kitsu as K; +use Aviat\AnimeClient\API\{FailedResponseException, Kitsu as K}; use Aviat\Ion\Json; trait KitsuTrait { @@ -142,8 +142,10 @@ trait KitsuTrait { { if ($logger) { - $logger->warning('Non 200 response for api call', (array)$response->getBody()); + $logger->warning('Non 200 response for api call', (array)$response); } + + throw new FailedResponseException('Failed to get the proper response from the API'); } return Json::decode($response->getBody(), TRUE); diff --git a/src/API/Kitsu/Model.php b/src/API/Kitsu/Model.php index db34552a..634a4ea8 100644 --- a/src/API/Kitsu/Model.php +++ b/src/API/Kitsu/Model.php @@ -160,13 +160,16 @@ class Model { */ public function getCharacter(string $slug): array { - // @todo catch non-existent characters and show 404 $data = $this->getRequest('/characters', [ 'query' => [ 'filter' => [ - 'name' => $slug + 'slug' => $slug, ], - // 'include' => 'primaryMedia,castings' + 'fields' => [ + 'anime' => 'canonicalTitle,titles,slug,posterImage', + 'manga' => 'canonicalTitle,titles,slug,posterImage' + ], + 'include' => 'castings.person,castings.media' ] ]); diff --git a/src/Controller/Character.php b/src/Controller/Character.php index 46bf2585..8f05563f 100644 --- a/src/Controller/Character.php +++ b/src/Controller/Character.php @@ -14,38 +14,128 @@ * @link https://git.timshomepage.net/timw4mail/HummingBirdAnimeClient */ -namespace Aviat\AnimeClient\Controller; - -use Aviat\AnimeClient\Controller as BaseController; - -/** - * Controller for character description pages - */ -class Character extends BaseController { - - public function index(string $slug) - { - $model = $this->container->get('kitsu-model'); - - $data = $model->getCharacter($slug); - - if (( ! array_key_exists('data', $data)) || empty($data['data'])) - { - return $this->notFound( - $this->formatTitle( - 'Characters', - 'Character not found' - ), - 'Character Not Found' - ); - } - - $this->outputHTML('character', [ - 'title' => $this->formatTitle( - 'Characters', - $data['data'][0]['attributes']['name'] - ), - 'data' => $data['data'][0]['attributes'] - ]); - } +namespace Aviat\AnimeClient\Controller; + +use Aviat\AnimeClient\Controller as BaseController; +use Aviat\AnimeClient\API\JsonAPI; +use Aviat\Ion\ArrayWrapper; + +/** + * Controller for character description pages + */ +class Character extends BaseController { + + use ArrayWrapper; + + public function index(string $slug) + { + $model = $this->container->get('kitsu-model'); + + $rawData = $model->getCharacter($slug); + + if (( ! array_key_exists('data', $rawData)) || empty($rawData['data'])) + { + return $this->notFound( + $this->formatTitle( + 'Characters', + 'Character not found' + ), + 'Character Not Found' + ); + } + + $data = JsonAPI::organizeData($rawData); + + $viewData = [ + 'title' => $this->formatTitle( + 'Characters', + $data[0]['attributes']['name'] + ), + 'data' => $data, + 'castings' => [] + ]; + + if (array_key_exists('included', $data) && array_key_exists('castings', $data['included'])) + { + $viewData['castings'] = $this->organizeCast($data['included']['castings']); + } + + $this->outputHTML('character', $viewData); + } + + private function dedupeCast(array $cast): array + { + $output = []; + $people = []; + + $i = 0; + foreach ($cast as &$role) + { + if (empty($role['attributes']['role'])) + { + continue; + } + + + $person = current($role['relationships']['person']['people'])['attributes']; + + if ( ! array_key_exists($person['name'], $people)) + { + $people[$person['name']] = $i; + $role['relationships']['media']['anime'] = [current($role['relationships']['media']['anime'])]; + $output[$i] = $role; + + $i++; + + continue; + } + else if(array_key_exists($person['name'], $people)) + { + if (array_key_exists('anime', $role['relationships']['media'])) + { + $key = $people[$person['name']]; + $output[$key]['relationships']['media']['anime'][] = current($role['relationships']['media']['anime']); + } + continue; + } + } + + return $output; + } + + private function organizeCast(array $cast): array + { + $cast = $this->dedupeCast($cast); + $output = []; + + foreach($cast as $id => $role) + { + if (empty($role['attributes']['role'])) + { + continue; + } + + $language = $role['attributes']['language']; + $roleName = $role['attributes']['role']; + $isVA = $role['attributes']['voiceActor']; + + if ($isVA) + { + $person = current($role['relationships']['person']['people'])['attributes']; + $name = $person['name']; + $item = [ + 'person' => $person, + 'series' => $role['relationships']['media']['anime'] + ]; + + $output[$roleName][$language][] = $item; + } + else + { + $output[$roleName][] = $role['relationships']['person']['people']; + } + } + + return $output; + } } \ No newline at end of file diff --git a/src/Dispatcher.php b/src/Dispatcher.php index 2e092c7d..9d56ba5f 100644 --- a/src/Dispatcher.php +++ b/src/Dispatcher.php @@ -26,6 +26,7 @@ use const Aviat\AnimeClient\{ use function Aviat\Ion\_dir; +use Aviat\AnimeClient\API\FailedResponseException; use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Friend; @@ -256,13 +257,24 @@ class Dispatcher extends RoutingBase { { $logger = $this->container->getLogger('default'); - $controller = new $controllerName($this->container); + try + { + $controller = new $controllerName($this->container); - // Run the appropriate controller method - $logger->debug('Dispatcher - controller arguments'); - $logger->debug(print_r($params, TRUE)); + // Run the appropriate controller method + $logger->debug('Dispatcher - controller arguments', $params); + + call_user_func_array([$controller, $method], $params); + } + catch (FailedResponseException $e) + { + $controllerName = DEFAULT_CONTROLLER; + $controller = new $controllerName($this->container); + $controller->errorPage(500, + 'API request timed out', + 'Failed to retrieve data from API ☹️'); + } - call_user_func_array([$controller, $method], $params); } /**