Episode incrementing and update work for anime

This commit is contained in:
Timothy Warren 2017-01-06 21:39:01 -05:00
parent 4c75701c0d
commit 8412588940
16 changed files with 278 additions and 152 deletions

View File

@ -20,6 +20,7 @@ use Aura\Html\HelperLocatorFactory;
use Aura\Router\RouterContainer;
use Aura\Session\SessionFactory;
use Aviat\AnimeClient\API\Kitsu\Auth as KitsuAuth;
use Aviat\AnimeClient\API\Kitsu\ListItem as KitsuListItem;
use Aviat\AnimeClient\API\Kitsu\KitsuModel;
use Aviat\AnimeClient\Model;
use Aviat\Banker\Pool;
@ -41,7 +42,10 @@ return function(array $config_array = []) {
$app_logger = new Logger('animeclient');
$app_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/app.log', Logger::NOTICE));
$request_logger = new Logger('request');
$request_logger->pushHandler(new RotatingFileHandler(__DIR__ . '/logs/request.log', Logger::NOTICE));
$container->setLogger($app_logger, 'default');
$container->setLogger($request_logger, 'request');
// -------------------------------------------------------------------------
// Injected Objects
@ -103,7 +107,9 @@ return function(array $config_array = []) {
// Models
$container->set('kitsu-model', function($container) {
$model = new KitsuModel();
$listItem = new KitsuListItem();
$listItem->setContainer($container);
$model = new KitsuModel($listItem);
$model->setContainer($container);
return $model;
});

View File

@ -203,7 +203,7 @@ return [
'index_redirect' => [
'path' => '/',
'controller' => AnimeClient::DEFAULT_CONTROLLER_NAMESPACE,
'action' => 'redirect_to_default',
'action' => 'redirectToDefaultRoute',
],
],
];

View File

@ -11,17 +11,17 @@
<section class="media-wrap">
<?php foreach($items as $item): ?>
<?php if ($item['private'] && ! $auth->is_authenticated()) continue; ?>
<article class="media" id="<?= $item['anime']['slug'] ?>">
<article class="media" id="<?= $item['id'] ?>">
<?php if ($auth->is_authenticated()): ?>
<button title="Increment episode count" class="plus_one" hidden>+1 Episode</button>
<?php endif ?>
<?= $helper->img($item['anime']['image']); ?>
<div class="name">
<a href="<?= $url->generate('anime.details', ['id' => $item['anime']['slug']]); ?>">
<?= array_shift($item['anime']['titles']) ?>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
<?= array_shift($item['anime']['titles']) ?>
<?php foreach ($item['anime']['titles'] as $title): ?>
<br /><small><?= $title ?></small>
<?php endforeach ?>
</a>
</div>
<div class="table">

View File

@ -76,7 +76,7 @@
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['anime']['slug'] ?>" name="id" />
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<input type="hidden" value="true" name="edit" />
<button type="submit">Submit</button>
</td>
@ -92,7 +92,7 @@
<tr>
<td>&nbsp;</td>
<td>
<input type="hidden" value="<?= $item['anime']['slug'] ?>" name="id" />
<input type="hidden" value="<?= $item['id'] ?>" name="id" />
<button type="submit" class="danger">Delete Entry</button>
</td>
</tr>

12
public/js/anime_edit.js Executable file → Normal file
View File

@ -15,19 +15,20 @@
// Setup the update data
let data = {
id: parent_sel.id,
increment_episodes: true
data: {
progress: watched_count + 1
}
};
// If the episode count is 0, and incremented,
// change status to currently watching
if (isNaN(watched_count) || watched_count === 0) {
data.status = 'currently-watching';
data.data.status = 'current';
}
// If you increment at the last episode, mark as completed
if (( ! isNaN(watched_count)) && (watched_count + 1) == total_count) {
delete data.increment_episodes;
data.status = 'completed';
data.data.status = 'completed';
}
// okay, lets actually make some changes!
@ -35,7 +36,6 @@
data: data,
dataType: 'json',
type: 'POST',
mimeType: 'application/json',
success: (res) => {
if (data.status == 'completed') {
_.hide(parent_sel);
@ -47,7 +47,7 @@
},
error: (xhr, errorType, error) => {
console.error(error);
_.showMessage('error', `Failed to updated ${title}. `);
_.showMessage('error', `Failed to update ${title}. `);
_.scrollToTop();
}
});

View File

@ -249,6 +249,7 @@ var AnimeClient = (function(w) {
config.type = config.type || 'GET';
config.dataType = config.dataType || '';
config.success = config.success || _.noop;
config.mimeType = config.mimeType || 'application/x-www-form-urlencoded';
config.error = config.error || _.noop;
let request = new XMLHttpRequest();
@ -280,14 +281,22 @@ var AnimeClient = (function(w) {
}
};
if (config.dataType === 'json') {
config.data = JSON.stringify(config.data);
config.mimeType = 'application/json';
} else {
config.data = ajaxSerialize(config.data);
}
request.setRequestHeader('Content-Type', config.mimeType);
switch (method) {
case "GET":
request.send(null);
break;
default:
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.send(ajaxSerialize(config.data));
request.send(config.data);
break;
}
};

View File

@ -22,6 +22,8 @@ use Aviat\AnimeClient\API\Kitsu\Transformer\{
AnimeTransformer, AnimeListTransformer, MangaTransformer, MangaListTransformer
};
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Exception\ClientException;
/**
* Kitsu API Model
@ -47,6 +49,11 @@ class KitsuModel {
*/
protected $animeTransformer;
/**
* @var ListItem
*/
protected $listItem;
/**
* @var MangaTransformer
*/
@ -60,13 +67,14 @@ class KitsuModel {
/**
* KitsuModel constructor.
*/
public function __construct()
public function __construct(ListItem $listItem)
{
// Set up Guzzle trait
$this->init();
$this->animeTransformer = new AnimeTransformer();
$this->animeListTransformer = new AnimeListTransformer();
$this->listItem = $listItem;
$this->mangaTransformer = new MangaTransformer();
$this->mangaListTransformer = new MangaListTransformer();
}
@ -124,7 +132,7 @@ class KitsuModel {
/**
* Get information about a particular manga
*
* @param string $animeId
* @param string $mangaId
* @return array
*/
public function getManga(string $mangaId): array
@ -133,29 +141,6 @@ class KitsuModel {
return $this->mangaTransformer->transform($baseData);
}
public function getListItem(string $listId): array
{
$baseData = $this->getRequest("library-entries/{$listId}", [
'query' => [
'include' => 'media'
]
]);
switch ($baseData['included'][0]['type'])
{
case 'anime':
$baseData['data']['anime'] = $baseData['included'][0];
return $this->animeListTransformer->transform($baseData['data']);
case 'manga':
$baseData['data']['manga'] = $baseData['included'][0];
return $this->mangaListTransformer->transform($baseData['data']);
default:
return $baseData['data']['attributes'];
}
}
public function getAnimeList($status): array
{
$options = [
@ -185,9 +170,9 @@ class KitsuModel {
$animeGenres = $item['anime']['relationships']['genres'];
foreach($animeGenres as $id)
{
$item['genres'][] = $included['genres'][$id]['name'];
}
{
$item['genres'][] = $included['genres'][$id]['name'];
}
// $item['genres'] = array_pluck($genres, 'name');
}
@ -238,10 +223,45 @@ class KitsuModel {
'include' => 'media'
];
$data = $this->getRequest($type, $options);
return $this->getRequest($type, $options);
}
// @TODO implement search api call
return $data;
public function getListItem(string $listId): array
{
$baseData = $this->listItem->get($listId);
switch ($baseData['included'][0]['type'])
{
case 'anime':
$baseData['data']['anime'] = $baseData['included'][0];
return $this->animeListTransformer->transform($baseData['data']);
case 'manga':
$baseData['data']['manga'] = $baseData['included'][0];
return $this->mangaListTransformer->transform($baseData['data']);
default:
return $baseData['data']['attributes'];
}
}
public function updateListItem(array $data)
{
try
{
$response = $this->listItem->update($data['id'], $data['data']);
return [
'statusCode' => $response->getStatusCode(),
'body' => $response->getBody(),
];
}
catch(ClientException $e)
{
return [
'statusCode' => $e->getResponse()->getStatusCode(),
'body' => Json::decode((string)$e->getResponse()->getBody())
];
}
}
private function getUsername(): string

View File

@ -21,7 +21,9 @@ use Aviat\AnimeClient\API\GuzzleTrait;
use Aviat\Ion\Json;
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Psr7\Response;
use InvalidArgumentException;
use PHP_CodeSniffer\Tokenizers\JS;
use RuntimeException;
trait KitsuTrait {
@ -33,6 +35,19 @@ trait KitsuTrait {
*/
protected $baseUrl = "https://kitsu.io/api/edge/";
/**
* HTTP headers to send with every request
*
* @var array
*/
protected $defaultHeaders = [
'User-Agent' => "Tim's Anime Client/4.0",
'Accept-Encoding' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json; charset=utf-8',
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151',
];
/**
* Set up the class properties
*
@ -42,11 +57,7 @@ trait KitsuTrait {
{
$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'
],
'headers' => $this->defaultHeaders,
'timeout' => 25,
'connect_timeout' => 25
];
@ -66,10 +77,11 @@ trait KitsuTrait {
* @param string $type
* @param string $url
* @param array $options
* @return array
* @return Response
*/
private function request(string $type, string $url, array $options = []): array
private function getResponse(string $type, string $url, array $options = [])
{
$logger = null;
$validTypes = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'];
if ( ! in_array($type, $validTypes))
@ -77,18 +89,13 @@ trait KitsuTrait {
throw new InvalidArgumentException('Invalid http request type');
}
$logger = NULL;
$defaultOptions = [
'headers' => [
'client_id' => 'dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd',
'client_secret' => '54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151'
]
'headers' => $this->defaultHeaders
];
if ($this->getContainer())
if ($this->getContainer());
{
$logger = $this->container->getLogger('default');
$logger = $this->container->getLogger('request');
$sessionSegment = $this->getContainer()
->get('session')
->getSegment(AnimeClient::SESSION_SEGMENT);
@ -98,11 +105,31 @@ trait KitsuTrait {
$token = $sessionSegment->get('auth_token');
$defaultOptions['headers']['Authorization'] = "bearer {$token}";
}
$logger->debug(Json::encode(func_get_args()));
}
$options = array_merge($defaultOptions, $options);
$response = $this->client->request($type, $url, $options);
return $this->client->request($type, $url, $options);
}
/**
* Make a request via Guzzle
*
* @param string $type
* @param string $url
* @param array $options
* @return array
*/
private function request(string $type, string $url, array $options = []): array
{
$logger = null;
if ($this->getContainer())
{
$logger = $this->container->getLogger('request');
}
$response = $this->getResponse($type, $url, $options);
if ((int) $response->getStatusCode() !== 200)
{
@ -112,7 +139,7 @@ trait KitsuTrait {
$logger->warning($response->getBody());
}
throw new RuntimeException($response);
// throw new RuntimeException($response->getBody());
}
return JSON::decode($response->getBody(), TRUE);
@ -129,6 +156,17 @@ trait KitsuTrait {
return $this->request('GET', ...$args);
}
/**
* Remove some boilerplate for patch requests
*
* @param array $args
* @return array
*/
protected function patchRequest(...$args): array
{
return $this->request('PATCH', ...$args);
}
/**
* Remove some boilerplate for post requests
*
@ -137,17 +175,38 @@ trait KitsuTrait {
*/
protected function postRequest(...$args): array
{
return $this->request('POST', ...$args);
$logger = null;
if ($this->getContainer())
{
$logger = $this->container->getLogger('request');
}
$response = $this->getResponse('POST', ...$args);
$validResponseCodes = [200, 201];
if ( ! in_array((int) $response->getStatusCode(), $validResponseCodes))
{
if ($logger)
{
$logger->warning('Non 201 response for POST api call');
$logger->warning($response->getBody());
}
throw new RuntimeException($response->getBody());
}
return JSON::decode($response->getBody(), TRUE);
}
/**
* Remove some boilerplate for delete requests
*
* @param array $args
* @return array
* @return bool
*/
protected function deleteRequest(...$args): array
protected function deleteRequest(...$args): bool
{
return $this->request('DELETE', ...$args);
$response = $this->getResponse('DELETE', ...$args);
return ((int) $response->getStatusCode() === 204);
}
}

View File

@ -17,27 +17,59 @@
namespace Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\AbstractListItem;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Json;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Psr7\Response;
use RuntimeException;
class KitsuAnimeListItem extends AbstractListItem {
/**
* CRUD operations for Kitsu list items
*/
class ListItem extends AbstractListItem {
use ContainerAware;
use KitsuTrait;
public function __construct()
{
$this->init();
}
public function create(array $data): bool
{
// TODO: Implement create() method.
return false;
}
public function delete(string $id): bool
{
// TODO: Implement delete() method.
return false;
}
public function get(string $id): array
{
// TODO: Implement get() method.
return $this->getRequest("library-entries/{$id}", [
'query' => [
'include' => 'media'
]
]);
}
public function update(string $id, array $data): bool
public function update(string $id, array $data): Response
{
// TODO: Implement update() method.
$requestData = [
'data' => [
'id' => $id,
'type' => 'libraryEntries',
'attributes' => $data
]
];
$response = $this->getResponse('PATCH', "library-entries/{$id}", [
'body' => JSON::encode($requestData)
]);
return $response;
}
}

View File

@ -80,15 +80,14 @@ class AnimeListTransformer extends AbstractTransformer {
*
* @param array $item Transformed library item
* @return array API library item
* @TODO reimplement
*/
public function untransform($item)
{
// Messy mapping of boolean values to their API string equivalents
$privacy = 'public';
$privacy = 'false';
if (array_key_exists('private', $item) && $item['private'])
{
$privacy = 'private';
$privacy = 'true';
}
$rewatching = 'false';
@ -97,16 +96,25 @@ class AnimeListTransformer extends AbstractTransformer {
$rewatching = 'true';
}
return [
$untransformed = [
'id' => $item['id'],
'status' => $item['watching_status'],
'sane_rating_update' => $item['user_rating'] / 2,
'rewatching' => $rewatching,
'rewatched_times' => $item['rewatched'],
'notes' => $item['notes'],
'episodes_watched' => $item['episodes_watched'],
'privacy' => $privacy
'data' => [
'status' => $item['watching_status'],
'rating' => $item['user_rating'] / 2,
'reconsuming' => $rewatching,
'reconsumeCount' => $item['rewatched'],
'notes' => $item['notes'],
'progress' => $item['episodes_watched'],
'private' => $privacy
]
];
if ((int) $untransformed['data']['rating'] === 0)
{
unset($untransformed['data']['rating']);
}
return $untransformed;
}
}
// End of AnimeListTransformer.php

View File

@ -1,29 +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;
/**
* 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

@ -16,6 +16,8 @@
namespace Aviat\AnimeClient\API;
use GuzzleHttp\Psr7\Response;
/**
* Common interface for anime and manga list item CRUD
*/
@ -42,9 +44,9 @@ interface ListItemInterface {
*
* @param string $id - The id of the list item to update
* @param array $data - The data with which to update the list item
* @return bool
* @return Response
*/
public function update(string $id, array $data): bool;
public function update(string $id, array $data): Response;
/**
* Delete a list item

View File

@ -120,7 +120,7 @@ class Controller {
*
* @return void
*/
public function redirect_to_default()
public function redirectToDefaultRoute()
{
$default_type = $this->config->get(['routes', 'route_config', 'default_list']);
$this->redirect($this->urlGenerator->default_url($default_type), 303);
@ -317,7 +317,7 @@ class Controller {
$auth = $this->container->get('auth');
$auth->logout();
$this->redirect_to_default();
$this->redirectToDefaultRoute();
}
/**
@ -358,7 +358,7 @@ class Controller {
* @param string $type
* @return void
*/
public function setFlashMessage($message, $type = "info")
public function set_flash_message($message, $type = "info")
{
$this->session->setFlash('message', [
'message_type' => $type,
@ -423,11 +423,12 @@ class Controller {
* @param int $code - the http status code
* @return void
*/
protected function outputJSON($data = [], $code = 200)
protected function outputJSON($data = 'Empty response', int $code = 200)
{
$view = new JsonView($this->container);
$view->setStatusCode($code);
$view->setOutput($data);
(new JsonView($this->container))
->setStatusCode($code)
->setOutput($data)
->send();
}
/**

View File

@ -21,6 +21,7 @@ use Aviat\AnimeClient\API\Kitsu;
use Aviat\AnimeClient\API\Kitsu\Enum\AnimeWatchingStatus;
use Aviat\AnimeClient\API\Kitsu\Transformer\AnimeListTransformer;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Json;
use Aviat\Ion\StringWrapper;
/**
@ -227,18 +228,12 @@ class Anime extends BaseController {
// large form-based updates
$transformer = new AnimeListTransformer();
$post_data = $transformer->untransform($data);
$full_result = $this->model->updateLibraryItem($post_data);
$full_result = $this->model->update($post_data);
$result = $full_result['body'];
if (array_key_exists('anime', $result))
if ($full_result['statusCode'] === 200)
{
$title = ( ! empty($result['anime']['alternate_title']))
? "{$result['anime']['title']} ({$result['anime']['alternate_title']})"
: "{$result['anime']['title']}";
$this->set_flash_message("Successfully updated {$title}.", 'success');
$this->cache->purge();
$this->set_flash_message("Successfully updated.", 'success');
// $this->cache->purge();
}
else
{
@ -255,8 +250,20 @@ class Anime extends BaseController {
*/
public function update()
{
$response = $this->model->update($this->request->getParsedBody());
$this->cache->purge();
if ($this->request->getHeader('content-type')[0] === 'application/json')
{
$data = JSON::decode((string)$this->request->getBody());
}
else
{
$data = $this->request->getParsedBody();
}
$response = $this->model->updateLibraryItem($data);
//echo JSON::encode($response);
//die();
// $this->cache->purge();
$this->outputJSON($response['body'], $response['statusCode']);
}

View File

@ -75,8 +75,8 @@ class Dispatcher extends RoutingBase {
$raw_route = $this->request->getUri()->getPath();
$route_path = "/" . trim($raw_route, '/');
$logger->debug('Dispatcher - Routing data from get_route method');
$logger->debug(print_r([
$logger->info('Dispatcher - Routing data from get_route method');
$logger->info(print_r([
'route_path' => $route_path
], TRUE));
@ -108,8 +108,8 @@ class Dispatcher extends RoutingBase {
{
$route = $this->getRoute();
$logger->debug('Dispatcher - Route invoke arguments');
$logger->debug(print_r($route, TRUE));
$logger->info('Dispatcher - Route invoke arguments');
$logger->info(print_r($route, TRUE));
}
if ($route)

View File

@ -81,6 +81,18 @@ class Anime extends API {
return $this->kitsuModel->getAnime($anime_id);
}
/**
* Search for anime by name
*
* @param string $name
* @return array
*/
public function search($name)
{
// $raw = $this->kitsuModel->search('anime', $name);
return $this->kitsuModel->search('anime', $name);
}
/**
* Get information about a specific list item
* for editing/updating that item
@ -94,15 +106,14 @@ class Anime extends API {
}
/**
* Search for anime by name
* Update a list entry
*
* @param string $name
* @param array $data
* @return array
*/
public function search($name)
public function updateLibraryItem(array $data): array
{
$raw = $this->kitsuModel->search('anime', $name);
return $this->kitsuModel->search('anime', $name);
return $this->kitsuModel->updateListItem($data);
}
}
// End of AnimeModel.php