Version 5.1 - All the GraphQL #32

Closed
timw4mail wants to merge 1160 commits from develop into master
18 changed files with 191 additions and 166 deletions
Showing only changes of commit c788cf5d87 - Show all commits

View File

@ -38,9 +38,8 @@ A self-hosted client that allows custom formatting of data from the hummingbird
### Installation ### Installation
1. Install dependencies via composer: `composer install` 1. Install dependencies via composer: `composer install`
2. Change the `WHOSE` constant declaration in `index.php` to your name 2. Configure settings in `app/config/config.php` and `app/config/routing.php` to your liking
3. Configure settings in `app/config/config.php` and `app/config/routing.php` to your liking 3. Create the following directories if they don't exist, and make sure they are world writable
4. Create the following directories if they don't exist, and make sure they are world writable
* app/cache * app/cache
* public/images/manga * public/images/manga
* public/images/anime * public/images/anime

View File

@ -5,6 +5,11 @@ $config = [
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
'hummingbird_username' => 'timw4mail', 'hummingbird_username' => 'timw4mail',
// ----------------------------------------------------------------------------
// Whose list is it?
// ----------------------------------------------------------------------------
'whose_list' => 'Tim',
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// General config // General config
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -1,6 +1,14 @@
<?php <?php
return [ return [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller',
'default_controller' => '\\Aviat\\AnimeClient\\Controller\\Anime',
'default_method' => 'index'
],
'configuration' => [
],
// Routes on all controllers // Routes on all controllers
'common' => [ 'common' => [
'update' => [ 'update' => [
@ -55,10 +63,6 @@ return [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
] ]
], ],
],
// Routes on stats controller
'stats' => [
], ],
// Routes on anime controller // Routes on anime controller
'anime' => [ 'anime' => [
@ -79,7 +83,6 @@ return [
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'all', 'type' => 'all',
'title' => WHOSE . " Anime List &middot; All"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -89,8 +92,7 @@ return [
'path' => '/anime/watching{/view}', 'path' => '/anime/watching{/view}',
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'currently-watching', 'type' => 'watching',
'title' => WHOSE . " Anime List &middot; Watching"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -100,8 +102,7 @@ return [
'path' => '/anime/plan_to_watch{/view}', 'path' => '/anime/plan_to_watch{/view}',
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'plan-to-watch', 'type' => 'plan_to_watch',
'title' => WHOSE . " Anime List &middot; Plan to Watch"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -111,8 +112,7 @@ return [
'path' => '/anime/on_hold{/view}', 'path' => '/anime/on_hold{/view}',
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'on-hold', 'type' => 'on_hold',
'title' => WHOSE . " Anime List &middot; On Hold"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -123,7 +123,6 @@ return [
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'dropped', 'type' => 'dropped',
'title' => WHOSE . " Anime List &middot; Dropped"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -134,7 +133,6 @@ return [
'action' => ['anime_list'], 'action' => ['anime_list'],
'params' => [ 'params' => [
'type' => 'completed', 'type' => 'completed',
'title' => WHOSE . " Anime List &middot; Completed"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -156,7 +154,6 @@ return [
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'all', 'type' => 'all',
'title' => WHOSE . " Manga List &middot; All"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -166,8 +163,7 @@ return [
'path' => '/manga/reading{/view}', 'path' => '/manga/reading{/view}',
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'Reading', 'type' => 'reading',
'title' => WHOSE . " Manga List &middot; Reading"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -177,8 +173,7 @@ return [
'path' => '/manga/plan_to_read{/view}', 'path' => '/manga/plan_to_read{/view}',
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'Plan to Read', 'type' => 'plan_to_read',
'title' => WHOSE . " Manga List &middot; Plan to Read"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -188,8 +183,7 @@ return [
'path' => '/manga/on_hold{/view}', 'path' => '/manga/on_hold{/view}',
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'On Hold', 'type' => 'on_hold',
'title' => WHOSE . " Manga List &middot; On Hold"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -199,8 +193,7 @@ return [
'path' => '/manga/dropped{/view}', 'path' => '/manga/dropped{/view}',
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'Dropped', 'type' => 'dropped',
'title' => WHOSE . " Manga List &middot; Dropped"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'
@ -210,8 +203,7 @@ return [
'path' => '/manga/completed{/view}', 'path' => '/manga/completed{/view}',
'action' => ['manga_list'], 'action' => ['manga_list'],
'params' => [ 'params' => [
'type' => 'Completed', 'type' => 'completed',
'title' => WHOSE . " Manga List &middot; Completed"
], ],
'tokens' => [ 'tokens' => [
'view' => '[a-z_]+' 'view' => '[a-z_]+'

View File

@ -13,14 +13,14 @@
<h1 class="flex flex-align-end flex-wrap"> <h1 class="flex flex-align-end flex-wrap">
<span class="flex-no-wrap grow-1"> <span class="flex-no-wrap grow-1">
<a href="<?= $urlGenerator->default_url($url_type) ?>"> <a href="<?= $urlGenerator->default_url($url_type) ?>">
<?= WHOSE ?> <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?> <?= $config->whose_list ?>'s <?= ucfirst($url_type) ?> <?= (strpos($route_path, 'collection') !== FALSE) ? 'Collection' : 'List' ?>
</a> [<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>] </a> [<a href="<?= $urlGenerator->default_url($other_type) ?>"><?= ucfirst($other_type) ?> List</a>]
</span> </span>
<span class="flex-no-wrap small-font"> <span class="flex-no-wrap small-font">
<?php if (is_logged_in()): ?> <?php if (is_logged_in()): ?>
[<a href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>] [<a href="<?= $urlGenerator->url("/{$url_type}/logout", $url_type) ?>">Logout</a>]
<?php else: ?> <?php else: ?>
[<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= WHOSE ?> Login</a>] [<a href="<?= $urlGenerator->url("/{$url_type}/login", $url_type) ?>"><?= $config->whose_list ?>'s Login</a>]
<?php endif ?> <?php endif ?>
</span> </span>
</h1> </h1>

View File

@ -3,20 +3,7 @@
* Here begins everything! * Here begins everything!
*/ */
// ----------------------------------------------------------------------------- session_start();
// ! Start config
// -----------------------------------------------------------------------------
/**
* Well, whose list is it?
*/
define('WHOSE', "Tim's");
// -----------------------------------------------------------------------------
// ! End config
// -----------------------------------------------------------------------------
\session_start();
// Work around the silly timezone error // Work around the silly timezone error
$timezone = ini_get('date.timezone'); $timezone = ini_get('date.timezone');
@ -43,16 +30,16 @@ function _dir()
return implode(DIRECTORY_SEPARATOR, func_get_args()); return implode(DIRECTORY_SEPARATOR, func_get_args());
} }
// Set up composer autoloader
require _dir(ROOT_DIR, '/vendor/autoload.php');
/** /**
* Set up autoloaders * Set up autoloaders
* *
* @codeCoverageIgnore * @codeCoverageIgnore
* @return void * @return void
*/ */
function _setup_autoloaders() spl_autoload_register(function ($class) {
{
require _dir(ROOT_DIR, '/vendor/autoload.php');
spl_autoload_register(function ($class) {
$class_parts = explode('\\', $class); $class_parts = explode('\\', $class);
$ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php"; $ns_path = SRC_DIR . '/' . implode('/', $class_parts) . ".php";
@ -61,11 +48,7 @@ function _setup_autoloaders()
require_once($ns_path); require_once($ns_path);
return; return;
} }
}); });
}
// Setup autoloaders
_setup_autoloaders();
// Do dependency injection, and go! // Do dependency injection, and go!
require _dir(APP_DIR, 'bootstrap.php'); require _dir(APP_DIR, 'bootstrap.php');

View File

@ -53,7 +53,6 @@ class Controller {
* Constructor * Constructor
* *
* @param Container $container * @param Container $container
* @param array $web
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {
@ -150,23 +149,6 @@ class Controller {
$this->response->content->set($buffer); $this->response->content->set($buffer);
} }
/**
* Output json with the proper content type
*
* @param mixed $data
* @return void
*/
public function outputJSON($data)
{
if ( ! is_string($data))
{
$data = json_encode($data);
}
$this->response->content->setType('application/json');
$this->response->content->set($data);
}
/** /**
* Redirect to the selected page * Redirect to the selected page
* *

View File

@ -50,6 +50,8 @@ class Anime extends BaseController {
/** /**
* Constructor * Constructor
*
* @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {
@ -89,17 +91,38 @@ class Anime extends BaseController {
* *
* @param string $type - The section of the list * @param string $type - The section of the list
* @param string $title - The title of the page * @param string $title - The title of the page
* @param string $view - List or cover view
* @return void * @return void
*/ */
public function anime_list($type, $title, $view) public function anime_list($type, $view)
{ {
$type_title_map = [
'all' => 'All',
'watching' => 'Currently Watching',
'plan_to_watch' => 'Plan to Watch',
'on_hold' => 'On Hold',
'dropped' => 'Dropped',
'completed' => 'Completed'
];
$model_map = [
'watching' => 'currently-watching',
'plan_to_watch' => 'plan-to-watch',
'on_hold' => 'on-hold',
'all' => 'all',
'dropped' => 'dropped',
'completed' => 'completed'
];
$title = $this->config->whose_list . "'s Anime List &middot; {$type_title_map[$type]}";
$view_map = [ $view_map = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list'
]; ];
$data = ($type != 'all') $data = ($type != 'all')
? $this->model->get_list($type) ? $this->model->get_list($model_map[$type])
: $this->model->get_all_lists(); : $this->model->get_all_lists();
$this->outputHTML('anime/' . $view_map[$view], [ $this->outputHTML('anime/' . $view_map[$view], [

View File

@ -8,6 +8,7 @@ namespace Aviat\AnimeClient\Controller;
use Aviat\AnimeClient\Container; use Aviat\AnimeClient\Container;
use Aviat\AnimeClient\Controller as BaseController; use Aviat\AnimeClient\Controller as BaseController;
use Aviat\AnimeClient\Config; use Aviat\AnimeClient\Config;
use Aviat\AnimeClient\UrlGenerator;
use Aviat\AnimeClient\Model\Anime as AnimeModel; use Aviat\AnimeClient\Model\Anime as AnimeModel;
use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel; use Aviat\AnimeClient\Model\AnimeCollection as AnimeCollectionModel;
@ -28,6 +29,12 @@ class Collection extends BaseController {
*/ */
protected $base_data; protected $base_data;
/**
* Url Generator class
* @var UrlGenerator
*/
protected $urlGenerator;
/** /**
* Route mapping for main navigation * Route mapping for main navigation
* @var array $nav_routes * @var array $nav_routes
@ -56,6 +63,7 @@ class Collection extends BaseController {
unset($this->nav_routes['Collection']); unset($this->nav_routes['Collection']);
} }
$this->urlGenerator = $container->get('url-generator');
$this->collection_model = new AnimeCollectionModel($container); $this->collection_model = new AnimeCollectionModel($container);
$this->base_data = array_merge($this->base_data, [ $this->base_data = array_merge($this->base_data, [
'message' => '', 'message' => '',
@ -93,7 +101,7 @@ class Collection extends BaseController {
$data = $this->collection_model->get_collection(); $data = $this->collection_model->get_collection();
$this->outputHTML('collection/' . $view_map[$view], [ $this->outputHTML('collection/' . $view_map[$view], [
'title' => WHOSE . " Anime Collection", 'title' => $this->config->whose_list . "'s Anime Collection",
'sections' => $data, 'sections' => $data,
'genres' => $this->collection_model->get_genre_list() 'genres' => $this->collection_model->get_genre_list()
]); ]);
@ -111,8 +119,8 @@ class Collection extends BaseController {
$this->outputHTML('collection/'. strtolower($action), [ $this->outputHTML('collection/'. strtolower($action), [
'action' => $action, 'action' => $action,
'action_url' => $this->config->full_url("collection/" . strtolower($action)), 'action_url' => $this->urlGenerator->full_url("collection/" . strtolower($action)),
'title' => WHOSE . " Anime Collection &middot; {$action}", 'title' => $this->config->whose_list . " Anime Collection &middot; {$action}",
'media_items' => $this->collection_model->get_media_type_list(), 'media_items' => $this->collection_model->get_media_type_list(),
'item' => ($action === "Edit") ? $this->collection_model->get($id) : [] 'item' => ($action === "Edit") ? $this->collection_model->get($id) : []
]); ]);

View File

@ -72,19 +72,29 @@ class Manga extends Controller {
* Get a section of the manga list * Get a section of the manga list
* *
* @param string $status * @param string $status
* @param string $title
* @param string $view * @param string $view
* @return void * @return void
*/ */
public function manga_list($status, $title, $view) public function manga_list($status, $view)
{ {
$map = [
'all' => 'All',
'plan_to_read' => 'Plan to Read',
'reading' => 'Reading',
'completed' => 'Completed',
'dropped' => 'Dropped',
'on_hold' => 'On Hold'
];
$title = $this->config->whose_list . "' Manga List &middot; {$map[$status]}";
$view_map = [ $view_map = [
'' => 'cover', '' => 'cover',
'list' => 'list' 'list' => 'list'
]; ];
$data = ($status !== 'all') $data = ($status !== 'all')
? [$status => $this->model->get_list($status)] ? [$map[$status] => $this->model->get_list($map[$status])]
: $this->model->get_all_lists(); : $this->model->get_all_lists();
$this->outputHTML('manga/' . $view_map[$view], [ $this->outputHTML('manga/' . $view_map[$view], [

View File

@ -25,6 +25,8 @@ class Model {
/** /**
* Constructor * Constructor
*
* @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {

View File

@ -33,6 +33,8 @@ class API extends \Aviat\AnimeClient\Model {
/** /**
* Constructor * Constructor
*
* @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {

View File

@ -28,6 +28,8 @@ class AnimeCollection extends DB {
/** /**
* Constructor * Constructor
*
* @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {

View File

@ -24,6 +24,8 @@ class DB extends \Aviat\AnimeClient\Model {
/** /**
* Constructor * Constructor
*
* @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {

View File

@ -15,7 +15,7 @@ class Stats extends DB {
/** /**
* Constructor * Constructor
* *
* @param Config $config * @param Container $container
*/ */
public function __construct(Container $container) public function __construct(Container $container)
{ {

View File

@ -24,12 +24,6 @@ class Router extends RoutingBase {
*/ */
protected $request; protected $request;
/**
* Array containing request and response objects
* @var array $web
*/
protected $web;
/** /**
* Routes added to router * Routes added to router
* @var array $output_routes * @var array $output_routes
@ -46,7 +40,6 @@ class Router extends RoutingBase {
parent::__construct($container); parent::__construct($container);
$this->router = $container->get('aura-router'); $this->router = $container->get('aura-router');
$this->request = $container->get('request'); $this->request = $container->get('request');
$this->web = [$this->request, $container->get('response')];
$this->output_routes = $this->_setup_routes(); $this->output_routes = $this->_setup_routes();
} }
@ -154,6 +147,35 @@ class Router extends RoutingBase {
return $controller; return $controller;
} }
/**
* Get the list of controllers in the default namespace
*
* @return array
*/
public function get_controller_list()
{
$convention_routing = $this->routes['convention'];
$default_namespace = $convention_routing['default_namespace'];
$path = str_replace('\\', '/', $default_namespace);
$path = trim($path, '/');
$actual_path = \_dir(SRC_DIR, $path);
$class_files = glob("{$actual_path}/*.php");
$controllers = [];
foreach($class_files as $file)
{
$raw_class_name = basename(str_replace(".php", "", $file));
$path = strtolower(basename($raw_class_name));
$class_name = trim($default_namespace . '\\' . $raw_class_name, '\\');
$controllers[$path] = $class_name;
}
return $controllers;
}
/** /**
* Select controller based on the current url, and apply its relevent routes * Select controller based on the current url, and apply its relevent routes
* *
@ -176,7 +198,8 @@ class Router extends RoutingBase {
$path = $route['path']; $path = $route['path'];
unset($route['path']); unset($route['path']);
$controller_class = '\\Aviat\\AnimeClient\\Controller\\' . ucfirst($route_type); $controller_map = $this->get_controller_list();
$controller_class = $controller_map[$route_type];
// Prepend the controller to the route parameters // Prepend the controller to the route parameters
array_unshift($route['action'], $controller_class); array_unshift($route['action'], $controller_class);

View File

@ -8,12 +8,16 @@ namespace Aviat\Ion\Base;
class Container { class Container {
/** /**
* Array with class instances
*
* @var array * @var array
*/ */
protected $container = []; protected $container = [];
/** /**
* Constructor * Constructor
*
* @param array $values (optional)
*/ */
public function __construct(array $values = []) public function __construct(array $values = [])
{ {

View File

@ -1,72 +0,0 @@
<?php
namespace Aviat\Ion\Base;
use Aura\Web\Request;
use Aura\Web\Response;
class Page {
/**
* @var Request
*/
protected $request;
/**
* @var Response
*/
protected $response;
/**
* __construct function.
*
* @param Request $request
* @param Response $response
*/
public function __construct(Request $request, Response $response)
{
$this->request = $request;
$this->response = $response;
}
/**
* __destruct function.
*/
public function __destruct()
{
$this->output();
}
/**
* Output the response to the client
*/
protected function output()
{
// send status
@header($this->response->status->get(), true, $this->response->status->getCode());
// headers
foreach($this->response->headers->get() as $label => $value)
{
@header("{$label}: {$value}");
}
// cookies
foreach($this->response->cookies->get() as $name => $cookie)
{
@setcookie(
$name,
$cookie['value'],
$cookie['expire'],
$cookie['path'],
$cookie['domain'],
$cookie['secure'],
$cookie['httponly']
);
}
// send the actual response
echo $this->response->content->get();
}
}
// End of Page.php

View File

@ -58,6 +58,9 @@ class RouterTest extends AnimeClient_TestCase {
{ {
$default_config = array( $default_config = array(
'routes' => [ 'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
],
'common' => [ 'common' => [
'login_form' => [ 'login_form' => [
'path' => '/login', 'path' => '/login',
@ -171,6 +174,9 @@ class RouterTest extends AnimeClient_TestCase {
'default_list' => 'manga' 'default_list' => 'manga'
], ],
'routes' => [ 'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
],
'common' => [ 'common' => [
'login_form' => [ 'login_form' => [
'path' => '/login', 'path' => '/login',
@ -207,4 +213,58 @@ class RouterTest extends AnimeClient_TestCase {
$this->assertEquals('//localhost/anime/watching', $this->urlGenerator->default_url('anime'), "Incorrect default url"); $this->assertEquals('//localhost/anime/watching', $this->urlGenerator->default_url('anime'), "Incorrect default url");
$this->assertEquals('', $this->urlGenerator->default_url('foo'), "Incorrect default url"); $this->assertEquals('', $this->urlGenerator->default_url('foo'), "Incorrect default url");
} }
public function dataGetControllerList()
{
return array(
'controller_list_sanity_check' => [
'config' => [
'routing' => [
'anime_path' => 'anime',
'manga_path' => 'manga',
'default_anime_path' => "/anime/watching",
'default_manga_path' => '/manga/all',
'default_list' => 'manga'
],
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\AnimeClient\\Controller'
]
]
],
'expected' => [
'anime' => 'Aviat\AnimeClient\Controller\Anime',
'manga' => 'Aviat\AnimeClient\Controller\Manga',
'collection' => 'Aviat\AnimeClient\Controller\Collection',
'stats' => 'Aviat\AnimeClient\Controller\Stats'
]
],
'empty_controller_list' => [
'config' => [
'routing' => [
'anime_path' => 'anime',
'manga_path' => 'manga',
'default_anime_path' => "/anime/watching",
'default_manga_path' => '/manga/all',
'default_list' => 'manga'
],
'routes' => [
'convention' => [
'default_namespace' => '\\Aviat\\Ion\\Controller'
]
]
],
'expected' => []
]
);
}
/**
* @dataProvider dataGetControllerList
*/
public function testGetControllerList($config, $expected)
{
$this->_set_up($config, '/', 'localhost');
$this->assertEquals($expected, $this->router->get_controller_list());
}
} }