Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
15 changed files with 417 additions and 125 deletions
Showing only changes of commit 0c910bff1a - Show all commits

View File

@ -0,0 +1,20 @@
################################################################################
# Cache Setup #
################################################################################
driver = "redis"
[connection]
# Host or socket to connect to
host = "127.0.0.1"
# Connection port
#port = 6379
# Connection password
#password = ""
# Database number
database = 2
[options]

View File

@ -1,20 +1,21 @@
<main> <main>
<?php if ($auth->is_authenticated()): ?> <?php /*if ($auth->is_authenticated()): ?>
<a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a> <a class="bracketed" href="<?= $url->generate('anime.add.get') ?>">Add Item</a>
<?php endif ?> <?php endif */ ?>
<?php if (empty($sections)): ?> <?php if (empty($sections)): ?>
<h3>There's nothing here!</h3> <h3>There's nothing here!</h3>
<?php else: ?> <?php else: ?>
<?php foreach ($sections as $name => $items): ?> <?php foreach ($sections as $name => $items): ?>
<?php /*<pre><?= print_r($items, TRUE) ?></pre> */ ?>
<section class="status"> <section class="status">
<h2><?= $escape->html($name) ?></h2> <h2><?= $escape->html($name) ?></h2>
<section class="media-wrap"> <section class="media-wrap">
<?php foreach($items as $item): ?> <?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?> <?php if ($item['private']/* && ! $auth->is_authenticated()*/) continue; ?>
<article class="media" id="<?= $item['anime']['slug'] ?>"> <article class="media" id="<?= $item['anime']['slug'] ?>">
<?php if ($auth->is_authenticated()): ?> <?php /* if ($auth->is_authenticated()): ?>
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button> <button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
<?php endif ?> <?php endif */ ?>
<?= $helper->img($item['anime']['image']); ?> <?= $helper->img($item['anime']['image']); ?>
<div class="name"> <div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>"> <a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
@ -23,13 +24,13 @@
</a> </a>
</div> </div>
<div class="table"> <div class="table">
<?php if ($auth->is_authenticated()): ?> <?php /* if ($auth->is_authenticated()): ?>
<div class="row"> <div class="row">
<span class="edit"> <span class="edit">
<a class="bracketed" title="Edit information about this anime" href="<?= $urlGenerator->url("anime/edit/{$item['id']}/{$item['watching_status']}") ?>">Edit</a> <a class="bracketed" title="Edit information about this anime" href="<?= $urlGenerator->url("anime/edit/{$item['id']}/{$item['watching_status']}") ?>">Edit</a>
</span> </span>
</div> </div>
<?php endif ?> <?php endif */ ?>
<?php if ($item['private'] || $item['rewatching']): ?> <?php if ($item['private'] || $item['rewatching']): ?>
<div class="row"> <div class="row">
<?php foreach(['private', 'rewatching'] as $attr): ?> <?php foreach(['private', 'rewatching'] as $attr): ?>
@ -64,6 +65,6 @@
<?php endforeach ?> <?php endforeach ?>
<?php endif ?> <?php endif ?>
</main> </main>
<?php if ($auth->is_authenticated()): ?> <?php /* if ($auth->is_authenticated()): ?>
<script src="<?= $urlGenerator->asset_url('js.php/g/edit') ?>"></script> <script src="<?= $urlGenerator->asset_url('js.php/g/edit') ?>"></script>
<?php endif ?> <?php endif */ ?>

View File

@ -17,7 +17,7 @@
[<a href="<?= $urlGenerator->default_url('manga') ?>">Manga List</a>] [<a href="<?= $urlGenerator->default_url('manga') ?>">Manga List</a>]
<?php endif ?> <?php endif ?>
</span> </span>
<?php if ($auth->is_authenticated()): ?> <?php /* if ($auth->is_authenticated()): ?>
<span class="flex-no-wrap">&nbsp;</span> <span class="flex-no-wrap">&nbsp;</span>
<span class="flex-no-wrap small-font"> <span class="flex-no-wrap small-font">
<button type="button" class="js-clear-cache user-btn">Clear API Cache</button> <button type="button" class="js-clear-cache user-btn">Clear API Cache</button>
@ -29,7 +29,7 @@
<a class="bracketed" href="<?= $url->generate('logout') ?>">Logout</a> <a class="bracketed" href="<?= $url->generate('logout') ?>">Logout</a>
<?php else: ?> <?php else: ?>
[<a href="<?= $url->generate('login'); ?>"><?= $config->get('whose_list') ?>'s Login</a>] [<a href="<?= $url->generate('login'); ?>"><?= $config->get('whose_list') ?>'s Login</a>]
<?php endif ?> <?php endif */ ?>
</span> </span>
</h1> </h1>
<nav> <nav>

View File

@ -18,6 +18,7 @@
"aura/html": "2.*", "aura/html": "2.*",
"aura/router": "3.*", "aura/router": "3.*",
"aura/session": "2.*", "aura/session": "2.*",
"aviat/banker": "dev-master",
"aviat/ion": "dev-master", "aviat/ion": "dev-master",
"filp/whoops": "2.0.*", "filp/whoops": "2.0.*",
"guzzlehttp/guzzle": "6.*", "guzzlehttp/guzzle": "6.*",
@ -45,4 +46,4 @@
"scripts": { "scripts": {
} }
} }

View File

@ -58,7 +58,7 @@ $whoops->pushHandler($defaultHandler);
//$whoops->pushHandler($jsonHandler); //$whoops->pushHandler($jsonHandler);
// Register as the error handler // Register as the error handler
$whoops->register(); // $whoops->register();
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Dependency Injection setup // Dependency Injection setup

86
src/API/GuzzleTrait.php Normal file
View File

@ -0,0 +1,86 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API;
use Psr\Http\Message\ResponseInterface;
/**
* Base trait for api interaction
*
* @method ResponseInterface get(string $uri, array $options);
* @method ResponseInterface delete(string $uri, array $options);
* @method ResponseInterface head(string $uri, array $options);
* @method ResponseInterface options(string $uri, array $options);
* @method ResponseInterface patch(string $uri, array $options);
* @method ResponseInterface post(string $uri, array $options);
* @method ResponseInterface put(string $uri, array $options);
*/
trait GuzzleTrait {
/**
* The Guzzle http client object
* @var object
*/
protected $client;
/**
* Cookie jar object for api requests
* @var object
*/
protected $cookieJar;
/**
* Set up the class properties
*
* @return void
*/
abstract protected function init();
/**
* Magic methods to call guzzle api client
*
* @param string $method
* @param array $args
* @return ResponseInterface|null
*/
public function __call($method, $args)
{
$valid_methods = [
'get',
'getAsync',
'delete',
'deleteAsync',
'head',
'headAsync',
'options',
'optionsAsync',
'patch',
'patchAsync',
'post',
'postAsync',
'put',
'putAsync'
];
if ( ! in_array($method, $valid_methods))
{
return NULL;
}
array_unshift($args, strtoupper($method));
return call_user_func_array([$this->client, 'request'], $args);
}
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\AbstractListItem;
class KitsuAnimeListItem extends AbstractListItem {
use KitsuTrait;
public function create(array $data): bool
{
// TODO: Implement create() method.
}
public function delete(string $id): bool
{
// TODO: Implement delete() method.
}
public function get(string $id): array
{
// TODO: Implement get() method.
}
public function update(string $id, array $data): bool
{
// TODO: Implement update() method.
}
}

View File

@ -0,0 +1,115 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\Ion\Json;
/**
* Kitsu API Model
*/
class KitsuModel {
use KitsuTrait;
const CLIENT_ID = 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd';
const CLIENT_SECRET = '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151';
/**
* Class to map anime list items
* to a common format used by
* templates
*
* @var AnimeListTransformer
*/
protected $animeListTransformer;
public function __construct()
{
// Set up Guzzle trait
$this->init();
$this->animeListTransformer = new AnimeListTransformer();
}
/**
* Get the access token from the Kitsu API
*
* @param string $username
* @param string $password
* @return bool|string
*/
public function authenticate(string $username, string $password)
{
$response = $this->post('https://kitsu.io/api/oauth/token', [
'body' => http_build_query([
'grant_type' => 'password',
'username' => $username,
'password' => $password,
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET
])
]);
$info = JSON::decode($response->getBody());
if (array_key_exists('access_token', $info)) {
// @TODO save token
return true;
}
return false;
}
public function getAnimeMedia($entryId): array {
$response = $this->get("library-entries/{$entryId}/media", [
'headers' => [
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET
]
]);
return JSON::decode($response->getBody(), TRUE);
}
public function getAnimeList(): array {
$response = $this->get('library-entries', [
'headers' => [
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET
],
'query' => [
'filter' => [
'user_id' => 2644,
'media_type' => 'Anime'
],
'include' => 'media'
]
]);
$data = JSON::decode($response->getBody(), TRUE);
foreach($data['data'] as &$item)
{
$item['anime'] = $this->getAnimeMedia($item['id'])['data']['attributes'];
}
$transformed = $this->animeListTransformer->transformCollection($data['data']);
return $transformed;
}
}

View File

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\GuzzleTrait;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
trait KitsuTrait {
use GuzzleTrait;
/**
* The base url for api requests
* @var string $base_url
*/
protected $baseUrl = "https://kitsu.io/api/edge/";
/**
* Set up the class properties
*
* @return void
*/
protected function init()
{
$this->cookieJar = new CookieJar();
$this->client = new Client([
'base_uri' => $this->baseUrl,
'cookies' => TRUE,
'http_errors' => TRUE,
'defaults' => [
'cookies' => $this->cookieJar,
'headers' => [
'User-Agent' => "Tim's Anime Client/4.0",
'Accept-Encoding' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json'
],
'timeout' => 25,
'connect_timeout' => 25
]
]);
}
}

View File

@ -1,68 +0,0 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\Model\API;
/**
* Kitsu API Model
*/
class Model extends API {
const CLIENT_ID = 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd';
const CLIENT_SECRET = '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151';
/**
* Base url for Kitsu API
*/
protected $baseUrl = 'https://kitsu.io/api/edge/';
/**
* Default settings for Guzzle
* @var array
*/
protected $connectionDefaults = [];
/**
* Get the access token from the Kitsu API
*
* @param string $username
* @param string $password
* @return bool|string
*/
public function authenticate(string $username, string $password)
{
$response = $this->post('https://kitsu.io/api/oauth/token', [
'body' => http_build_query([
'grant_type' => 'password',
'username' => $username,
'password' => $password,
'client_id' => self::CLIENT_ID,
'client_secret' => self::CLIENT_SECRET
])
]);
$info = $response->getBody();
if (array_key_exists('access_token', $info)) {
// @TODO save token
return true;
}
return false;
}
}

View File

@ -32,65 +32,61 @@ class AnimeListTransformer extends AbstractTransformer {
*/ */
public function transform($item) public function transform($item)
{ {
print_r($item); /* ?><pre><?= print_r($item, TRUE) ?></pre><?php
die(); die(); */
// $anime = [];
$genres = [];
$anime =& $item['anime']; $anime =& $item['anime'];
$genres = $this->linearizeGenres($item['anime']['genres']); // $genres = $this->linearizeGenres($item['anime']['genres']); */
$rating = NULL; $rating = (int) 2 * $item['attributes']['rating'];
if ($item['rating']['type'] === 'advanced')
{
$rating = is_numeric($item['rating']['value'])
? (int) 2 * $item['rating']['value']
: '-';
}
$total_episodes = is_numeric($anime['episode_count']) $total_episodes = is_numeric($anime['episodeCount'])
? $anime['episode_count'] ? $anime['episodeCount']
: '-'; : '-';
$alternate_title = NULL; $alternate_title = NULL;
if (array_key_exists('alternate_title', $anime)) if (array_key_exists('en_jp', $anime['titles']))
{ {
// If the alternate title is very similar, or // If the alternate title is very similar, or
// a subset of the main title, don't list the // a subset of the main title, don't list the
// alternate title // alternate title
$not_subset = stripos($anime['title'], $anime['alternate_title']) === FALSE; $not_subset = stripos($anime['canonicalTitle'], $anime['titles']['en_jp']) === FALSE;
$diff = levenshtein($anime['title'], $anime['alternate_title'] ?? ''); $diff = levenshtein($anime['canonicalTitle'], $anime['titles']['en_jp'] ?? '');
if ($not_subset && $diff >= 5) if ($not_subset && $diff >= 5)
{ {
$alternate_title = $anime['alternate_title']; $alternate_title = $anime['titles']['en_jp'];
} }
} }
return [ return [
'id' => $item['id'], 'id' => $item['id'],
'episodes' => [ 'episodes' => [
'watched' => $item['episodes_watched'], 'watched' => $item['attributes']['progress'],
'total' => $total_episodes, 'total' => $total_episodes,
'length' => $anime['episode_length'], 'length' => $anime['episodeLength'],
], ],
'airing' => [ 'airing' => [
'status' => $anime['status'], 'status' => $anime['status'] ?? '',
'started' => $anime['started_airing'], 'started' => $anime['startDate'],
'ended' => $anime['finished_airing'] 'ended' => $anime['endDate']
], ],
'anime' => [ 'anime' => [
'age_rating' => $anime['age_rating'], 'age_rating' => $anime['ageRating'],
'title' => $anime['title'], 'title' => $anime['canonicalTitle'],
'alternate_title' => $alternate_title, 'alternate_title' => $alternate_title,
'slug' => $anime['slug'], 'slug' => $anime['slug'],
'url' => $anime['url'], 'url' => $anime['url'] ?? '',
'type' => $anime['show_type'], 'type' => $anime['showType'],
'image' => $anime['cover_image'], 'image' => $anime['posterImage']['small'],
'genres' => $genres, 'genres' => [],//$genres,
], ],
'watching_status' => $item['status'], 'watching_status' => $item['attributes']['status'],
'notes' => $item['notes'], 'notes' => $item['attributes']['notes'],
'rewatching' => (bool) $item['rewatching'], 'rewatching' => (bool) $item['attributes']['reconsuming'],
'rewatched' => $item['rewatched_times'], 'rewatched' => (int) $item['attributes']['reconsumeCount'],
'user_rating' => $rating, 'user_rating' => $rating,
'private' => (bool) $item['private'], 'private' => (bool) $item['attributes']['private'] ?? false,
]; ];
} }

29
src/API/ListInterface.php Normal file
View File

@ -0,0 +1,29 @@
<?php declare(strict_types=1);
/**
* Anime List Client
*
* An API client for Kitsu and MyAnimeList to manage anime and manga watch lists
*
* PHP version 7
*
* @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient
*/
namespace Aviat\AnimeClient\API;
/**
* Common interface for anime and manga list item CRUD
*/
interface ListInterface {
/**
* Get the raw list of items
*
* @return array
*/
public function get(): array;
}

View File

@ -40,8 +40,8 @@ interface ListItemInterface {
/** /**
* Update a list item * Update a list item
* *
* @param string $id - The id of the list tiem to update * @param string $id - The id of the list item to update
* @param array $data - The data with which to update the list itme * @param array $data - The data with which to update the list item
* @return bool * @return bool
*/ */
public function update(string $id, array $data): bool; public function update(string $id, array $data): bool;

View File

@ -17,16 +17,17 @@
namespace Aviat\AnimeClient\Controller; namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Kitsu\Enum\AnimeWatchingStatus; use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus;
use Aviat\AnimeClient\Kitsu\Transformer\AnimeListTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\Ion\Di\ContainerInterface; use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\StringWrapper;
/** /**
* Controller for Anime-related pages * Controller for Anime-related pages
*/ */
class Anime extends BaseController { class Anime extends BaseController {
use \Aviat\Ion\StringWrapper; use StringWrapper;
/** /**
* The anime list model * The anime list model

View File

@ -6,20 +6,23 @@
* *
* PHP version 7 * PHP version 7
* *
* @package AnimeListClient * @package AnimeListClient
* @author Timothy J. Warren <tim@timshomepage.net> * @author Timothy J. Warren <tim@timshomepage.net>
* @copyright 2015 - 2016 Timothy J. Warren * @copyright 2015 - 2016 Timothy J. Warren
* @license http://www.opensource.org/licenses/mit-license.html MIT License * @license http://www.opensource.org/licenses/mit-license.html MIT License
* @version 4.0 * @version 4.0
* @link https://github.com/timw4mail/HummingBirdAnimeClient * @link https://github.com/timw4mail/HummingBirdAnimeClient
*/ */
namespace Aviat\AnimeClient\Model; namespace Aviat\AnimeClient\Model;
use Aviat\AnimeClient\API\Kitsu\KitsuModel;
use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus; use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer; use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json; use Aviat\Ion\Json;
/** /**
* Model for handling requests dealing with the anime list * Model for handling requests dealing with the anime list
*/ */
@ -50,6 +53,12 @@ class Anime extends API {
AnimeWatchingStatus::COMPLETED => self::COMPLETED, AnimeWatchingStatus::COMPLETED => self::COMPLETED,
]; ];
public function __construct(ContainerInterface $container) {
parent::__construct($container);
$this->kitsuModel = new KitsuModel();
}
/** /**
* Update the selected anime * Update the selected anime
* *
@ -143,8 +152,11 @@ class Anime extends API {
*/ */
public function get_list($status) public function get_list($status)
{ {
$data = $this->_get_list_from_api($status); $data = $this->kitsuModel->getAnimeList();
$this->sort_by_name($data, 'anime'); //return JSON::decode((string)$stream, TRUE);
/*$data = $this->_get_list_from_api($status);
$this->sort_by_name($data, 'anime');*/
$output = []; $output = [];
$output[$this->const_map[$status]] = $data; $output[$this->const_map[$status]] = $data;