Full Anilist settings page OAuth flow, ability to run app without manually editing config files. See #7. Resolves #5
This commit is contained in:
parent
7a2bb1ba05
commit
fec30b7e36
@ -30,6 +30,14 @@ return array_merge($tomlConfig, [
|
|||||||
'asset_dir' => "{$ROOT_DIR}/public",
|
'asset_dir' => "{$ROOT_DIR}/public",
|
||||||
'base_config_dir' => __DIR__,
|
'base_config_dir' => __DIR__,
|
||||||
'config_dir' => "{$APP_DIR}/config",
|
'config_dir' => "{$APP_DIR}/config",
|
||||||
|
|
||||||
|
// No config defaults
|
||||||
|
'kitsu_username' => 'timw4mail',
|
||||||
|
'whose_list' => 'Someone',
|
||||||
|
'cache' => [
|
||||||
|
'connection' => [],
|
||||||
|
'driver' => 'null',
|
||||||
|
],
|
||||||
|
|
||||||
// Routing defaults
|
// Routing defaults
|
||||||
'asset_path' => '/public',
|
'asset_path' => '/public',
|
||||||
|
@ -187,11 +187,13 @@ return [
|
|||||||
'path' => '/anilist-redirect',
|
'path' => '/anilist-redirect',
|
||||||
'action' => 'anilistRedirect',
|
'action' => 'anilistRedirect',
|
||||||
'controller' => DEFAULT_CONTROLLER,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
|
'verb' => 'get',
|
||||||
],
|
],
|
||||||
'anilist-oauth' => [
|
'anilist-callback' => [
|
||||||
'path' => '/anilist-oauth',
|
'path' => '/anilist-oauth',
|
||||||
'action' => 'anilistCallback',
|
'action' => 'anilistCallback',
|
||||||
'controller' => DEFAULT_CONTROLLER,
|
'controller' => DEFAULT_CONTROLLER,
|
||||||
|
'verb' => 'get',
|
||||||
],
|
],
|
||||||
'image_proxy' => [
|
'image_proxy' => [
|
||||||
'path' => '/public/images/{type}/{file}',
|
'path' => '/public/images/{type}/{file}',
|
||||||
|
@ -3,14 +3,6 @@
|
|||||||
// $fields
|
// $fields
|
||||||
// $hiddenFields
|
// $hiddenFields
|
||||||
// $nestedPrefix
|
// $nestedPrefix
|
||||||
if ( ! function_exists('subfieldRender'))
|
|
||||||
{
|
|
||||||
function subfieldRender ($nestedPrefix, $fields, &$hiddenFields, $helper, $section)
|
|
||||||
{
|
|
||||||
include '_form.php';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<?php foreach ($fields as $name => $field): ?>
|
<?php foreach ($fields as $name => $field): ?>
|
||||||
@ -18,7 +10,7 @@ if ( ! function_exists('subfieldRender'))
|
|||||||
<?php if ($field['type'] === 'subfield'): ?>
|
<?php if ($field['type'] === 'subfield'): ?>
|
||||||
<section>
|
<section>
|
||||||
<h4><?= $field['title'] ?></h4>
|
<h4><?= $field['title'] ?></h4>
|
||||||
<?php subfieldRender($fieldname, $field['fields'], $hiddenFields, $helper, $section); ?>
|
<?php include_once '_form.php'; ?>
|
||||||
</section>
|
</section>
|
||||||
<?php elseif ( ! empty($field['display'])): ?>
|
<?php elseif ( ! empty($field['display'])): ?>
|
||||||
<article>
|
<article>
|
||||||
|
@ -43,7 +43,7 @@ $hasManga = stripos($_SERVER['REQUEST_URI'], 'manga') !== FALSE;
|
|||||||
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
|
[<?= $helper->a($urlGenerator->defaultUrl('anime') . $extraSegment, 'Anime List') ?>]
|
||||||
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
|
[<?= $helper->a($urlGenerator->defaultUrl('manga') . $extraSegment, 'Manga List') ?>]
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
<?php if ($auth->isAuthenticated()): ?>
|
<?php if ($auth->isAuthenticated() && $config->get(['cache', 'driver']) !== 'null'): ?>
|
||||||
<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>
|
||||||
</span>
|
</span>
|
||||||
|
@ -29,13 +29,34 @@ $nestedPrefix = 'config';
|
|||||||
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
|
<label for="settings-tab<?= $i ?>"><h3><?= $sectionMapping[$section] ?></h3></label>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<?php require __DIR__ . '/_form.php' ?>
|
<?php require __DIR__ . '/_form.php' ?>
|
||||||
|
<?php if ($section === 'anilist'): ?>
|
||||||
|
<hr />
|
||||||
|
<?php $auth = $anilistModel->checkAuth(); ?>
|
||||||
|
<?php if (array_key_exists('errors', $auth)): ?>
|
||||||
|
<p class="static-message error">Not Authorized.</p>
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('anilist-redirect'),
|
||||||
|
'Link Anilist Account'
|
||||||
|
) ?>
|
||||||
|
<?php else: ?>
|
||||||
|
<?php $expires = $config->get(['anilist', 'access_token_expires']); ?>
|
||||||
|
<p class="static-message info">
|
||||||
|
Linked to Anilist. Your access token will expire around <?= date('F j, Y, g:i a T', $expires) ?>
|
||||||
|
</p>
|
||||||
|
<?= $helper->a(
|
||||||
|
$url->generate('anilist-redirect'),
|
||||||
|
'Update Access Token'
|
||||||
|
) ?>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
<?php endif ?>
|
||||||
</section>
|
</section>
|
||||||
<?php $i++; ?>
|
<?php $i++; ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<?php foreach ($hiddenFields as $field): ?>
|
<?php foreach ($hiddenFields as $field): ?>
|
||||||
<?= $field ?>
|
<?= $field->__toString() ?>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
<button type="submit">Save Changes</button>
|
<button type="submit">Save Changes</button>
|
||||||
</main>
|
</main>
|
||||||
|
2
public/css/app.min.css
vendored
2
public/css/app.min.css
vendored
File diff suppressed because one or more lines are too long
@ -310,7 +310,7 @@ a:hover, a:active {
|
|||||||
Message boxes
|
Message boxes
|
||||||
------------------------------------------------------------------------------*/
|
------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.message {
|
.message, .static-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.5em auto;
|
margin: 0.5em auto;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
@ -342,7 +342,7 @@ a:hover, a:active {
|
|||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.error {
|
.message.error, .static-message.error {
|
||||||
border: 1px solid #924949;
|
border: 1px solid #924949;
|
||||||
background: #f3e6e6;
|
background: #f3e6e6;
|
||||||
}
|
}
|
||||||
@ -351,7 +351,7 @@ a:hover, a:active {
|
|||||||
content: '✘';
|
content: '✘';
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.success {
|
.message.success, .static-message.success {
|
||||||
border: 1px solid #1f8454;
|
border: 1px solid #1f8454;
|
||||||
background: #70dda9;
|
background: #70dda9;
|
||||||
}
|
}
|
||||||
@ -360,7 +360,7 @@ a:hover, a:active {
|
|||||||
content: '✔'
|
content: '✔'
|
||||||
}
|
}
|
||||||
|
|
||||||
.message.info {
|
.message.info, .static-message.info {
|
||||||
border: 1px solid #bfbe3a;
|
border: 1px solid #bfbe3a;
|
||||||
background: #FFFFCC;
|
background: #FFFFCC;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ use Aviat\AnimeClient\API\Enum\{
|
|||||||
*/
|
*/
|
||||||
final class Anilist {
|
final class Anilist {
|
||||||
public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize';
|
public const AUTH_URL = 'https://anilist.co/api/v2/oauth/authorize';
|
||||||
|
public const TOKEN_URL = 'https://anilist.co/api/v2/oauth/token';
|
||||||
public const BASE_URL = 'https://graphql.anilist.co';
|
public const BASE_URL = 'https://graphql.anilist.co';
|
||||||
|
|
||||||
public const KITSU_ANILIST_WATCHING_STATUS_MAP = [
|
public const KITSU_ANILIST_WATCHING_STATUS_MAP = [
|
||||||
|
@ -16,15 +16,17 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Anilist;
|
namespace Aviat\AnimeClient\API\Anilist;
|
||||||
|
|
||||||
|
use const Aviat\AnimeClient\USER_AGENT;
|
||||||
|
|
||||||
|
use function Amp\Promise\wait;
|
||||||
|
|
||||||
use Amp\Artax\Request;
|
use Amp\Artax\Request;
|
||||||
use Amp\Artax\Response;
|
use Amp\Artax\Response;
|
||||||
use function Amp\Promise\wait;
|
|
||||||
|
|
||||||
use Aviat\AnimeClient\API\{
|
use Aviat\AnimeClient\API\{
|
||||||
Anilist,
|
Anilist,
|
||||||
HummingbirdClient
|
HummingbirdClient
|
||||||
};
|
};
|
||||||
use const Aviat\AnimeClient\SESSION_SEGMENT;
|
|
||||||
use Aviat\Ion\Json;
|
use Aviat\Ion\Json;
|
||||||
use Aviat\Ion\Di\ContainerAware;
|
use Aviat\Ion\Di\ContainerAware;
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ trait AnilistTrait {
|
|||||||
'Accept' => 'application/json',
|
'Accept' => 'application/json',
|
||||||
'Accept-Encoding' => 'gzip',
|
'Accept-Encoding' => 'gzip',
|
||||||
'Content-type' => 'application/json',
|
'Content-type' => 'application/json',
|
||||||
'User-Agent' => "Tim's Anime Client/4.0"
|
'User-Agent' => USER_AGENT,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,13 +82,10 @@ trait AnilistTrait {
|
|||||||
$anilistConfig = $config->get('anilist');
|
$anilistConfig = $config->get('anilist');
|
||||||
|
|
||||||
$request = $this->requestBuilder->newRequest('POST', $url);
|
$request = $this->requestBuilder->newRequest('POST', $url);
|
||||||
$sessionSegment = $this->getContainer()
|
|
||||||
->get('session')
|
|
||||||
->getSegment(SESSION_SEGMENT);
|
|
||||||
|
|
||||||
//$authenticated = $sessionSegment->get('auth_token') !== NULL;
|
// You can only authenticate the request if you
|
||||||
|
// actually have an access_token saved
|
||||||
//if ($authenticated)
|
if ($config->has(['anilist', 'access_token']))
|
||||||
{
|
{
|
||||||
$request = $request->setAuth('bearer', $anilistConfig['access_token']);
|
$request = $request->setAuth('bearer', $anilistConfig['access_token']);
|
||||||
}
|
}
|
||||||
@ -245,7 +244,7 @@ trait AnilistTrait {
|
|||||||
{
|
{
|
||||||
$response = $this->getResponse(Anilist::BASE_URL, $options);
|
$response = $this->getResponse(Anilist::BASE_URL, $options);
|
||||||
$validResponseCodes = [200, 201];
|
$validResponseCodes = [200, 201];
|
||||||
|
|
||||||
$logger = NULL;
|
$logger = NULL;
|
||||||
if ($this->getContainer())
|
if ($this->getContainer())
|
||||||
{
|
{
|
||||||
|
6
src/API/Anilist/GraphQL/Queries/CheckLogin.graphql
Normal file
6
src/API/Anilist/GraphQL/Queries/CheckLogin.graphql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
query {
|
||||||
|
Viewer {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
@ -16,11 +16,15 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\API\Anilist;
|
namespace Aviat\AnimeClient\API\Anilist;
|
||||||
|
|
||||||
|
use function Amp\Promise\wait;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
|
|
||||||
use Amp\Artax\Request;
|
use Amp\Artax\Request;
|
||||||
|
use Aviat\AnimeClient\API\Anilist;
|
||||||
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
use Aviat\AnimeClient\API\Mapping\{AnimeWatchingStatus, MangaReadingStatus};
|
||||||
use Aviat\AnimeClient\Types\FormItem;
|
use Aviat\AnimeClient\Types\FormItem;
|
||||||
|
use Aviat\Ion\Json;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anilist API Model
|
* Anilist API Model
|
||||||
@ -47,6 +51,42 @@ final class Model
|
|||||||
// ! Generic API calls
|
// ! Generic API calls
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to get an auth token
|
||||||
|
*
|
||||||
|
* @param string $code - The request token
|
||||||
|
* @param string $redirectUri - The oauth callback url
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function authenticate(string $code, string $redirectUri): array
|
||||||
|
{
|
||||||
|
$config = $this->getContainer()->get('config');
|
||||||
|
$request = $this->requestBuilder
|
||||||
|
->newRequest('POST', Anilist::TOKEN_URL)
|
||||||
|
->setJsonBody([
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'client_id' => $config->get(['anilist', 'client_id']),
|
||||||
|
'client_secret' => $config->get(['anilist', 'client_secret']),
|
||||||
|
'redirect_uri' => $redirectUri,
|
||||||
|
'code' => $code,
|
||||||
|
])
|
||||||
|
->getFullRequest();
|
||||||
|
|
||||||
|
$response = $this->getResponseFromRequest($request);
|
||||||
|
|
||||||
|
return Json::decode(wait($response->getBody()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check auth status with simple API call
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function checkAuth(): array
|
||||||
|
{
|
||||||
|
return $this->runQuery('CheckLogin');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get user list data for syncing with Kitsu
|
* Get user list data for syncing with Kitsu
|
||||||
*
|
*
|
||||||
|
@ -170,9 +170,11 @@ final class Auth {
|
|||||||
*/
|
*/
|
||||||
public function get_auth_token()
|
public function get_auth_token()
|
||||||
{
|
{
|
||||||
|
$now = time();
|
||||||
|
|
||||||
$token = $this->segment->get('auth_token', FALSE);
|
$token = $this->segment->get('auth_token', FALSE);
|
||||||
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
$refreshToken = $this->segment->get('refresh_token', FALSE);
|
||||||
$isExpired = time() >= $this->segment->get('auth_token_expires', 0);
|
$isExpired = time() > $this->segment->get('auth_token_expires', $now + 5000);
|
||||||
|
|
||||||
// Attempt to re-authenticate with refresh token
|
// Attempt to re-authenticate with refresh token
|
||||||
/* if ($isExpired && $refreshToken)
|
/* if ($isExpired && $refreshToken)
|
||||||
|
0
src/API/Kitsu/GraphQL/Mutations/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Mutations/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Queries/.gitkeep
Normal file
0
src/API/Kitsu/GraphQL/Queries/.gitkeep
Normal file
@ -56,26 +56,6 @@ function loadToml(string $path): array
|
|||||||
return $output;
|
return $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Load configuration from toml files, keyed by the original file
|
|
||||||
*
|
|
||||||
* @param string $path
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
function loadTomlByFile(string $path): array
|
|
||||||
{
|
|
||||||
$output = [];
|
|
||||||
$files = glob("{$path}/*.toml");
|
|
||||||
|
|
||||||
foreach ($files as $file)
|
|
||||||
{
|
|
||||||
$config = Toml::parseFile($file);
|
|
||||||
$output[basename($file)] = $config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load config from one specific TOML file
|
* Load config from one specific TOML file
|
||||||
*
|
*
|
||||||
@ -179,6 +159,7 @@ function checkFolderPermissions(ConfigInterface $config): array
|
|||||||
$publicDir = $config->get('asset_dir');
|
$publicDir = $config->get('asset_dir');
|
||||||
|
|
||||||
$pathMap = [
|
$pathMap = [
|
||||||
|
'app/config' => realpath(__DIR__ . '/../app/config'),
|
||||||
'app/logs' => realpath(__DIR__ . '/../app/logs'),
|
'app/logs' => realpath(__DIR__ . '/../app/logs'),
|
||||||
'public/images/avatars' => "{$publicDir}/images/avatars",
|
'public/images/avatars' => "{$publicDir}/images/avatars",
|
||||||
'public/images/anime' => "{$publicDir}/images/anime",
|
'public/images/anime' => "{$publicDir}/images/anime",
|
||||||
|
@ -70,12 +70,18 @@ class BaseCommand extends Command {
|
|||||||
$APP_DIR = realpath(__DIR__ . '/../../app');
|
$APP_DIR = realpath(__DIR__ . '/../../app');
|
||||||
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
$APPCONF_DIR = realpath("{$APP_DIR}/appConf/");
|
||||||
$CONF_DIR = realpath("{$APP_DIR}/config/");
|
$CONF_DIR = realpath("{$APP_DIR}/config/");
|
||||||
$base_config = require $APPCONF_DIR . '/base_config.php';
|
$baseConfig = require $APPCONF_DIR . '/base_config.php';
|
||||||
|
|
||||||
$config = loadToml($CONF_DIR);
|
$config = loadToml($CONF_DIR);
|
||||||
$config_array = array_merge($base_config, $config);
|
|
||||||
|
|
||||||
$di = function ($config_array) use ($APP_DIR) {
|
$overrideFile = $CONF_DIR . '/admin-override.toml';
|
||||||
|
$overrideConfig = file_exists($overrideFile)
|
||||||
|
? loadTomlFile($overrideFile)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
$configArray = array_replace_recursive($baseConfig, $config, $overrideConfig);
|
||||||
|
|
||||||
|
$di = function ($configArray) use ($APP_DIR) {
|
||||||
$container = new Container();
|
$container = new Container();
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -93,8 +99,8 @@ class BaseCommand extends Command {
|
|||||||
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
$container->setLogger($kitsu_request_logger, 'kitsu-request');
|
||||||
|
|
||||||
// Create Config Object
|
// Create Config Object
|
||||||
$container->set('config', function() use ($config_array) {
|
$container->set('config', function() use ($configArray) {
|
||||||
return new Config($config_array);
|
return new Config($configArray);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create Cache Object
|
// Create Cache Object
|
||||||
@ -148,6 +154,6 @@ class BaseCommand extends Command {
|
|||||||
return $container;
|
return $container;
|
||||||
};
|
};
|
||||||
|
|
||||||
return $di($config_array);
|
return $di($configArray);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -60,6 +60,16 @@ final class SyncLists extends BaseCommand {
|
|||||||
{
|
{
|
||||||
$this->setContainer($this->setupContainer());
|
$this->setContainer($this->setupContainer());
|
||||||
$this->setCache($this->container->get('cache'));
|
$this->setCache($this->container->get('cache'));
|
||||||
|
|
||||||
|
$config = $this->container->get('config');
|
||||||
|
$anilistEnabled = $config->get(['anilist', 'enabled']);
|
||||||
|
|
||||||
|
if ( ! $anilistEnabled)
|
||||||
|
{
|
||||||
|
$this->echoBox('Anlist API is not enabled. Can not sync.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->anilistModel = $this->container->get('anilist-model');
|
$this->anilistModel = $this->container->get('anilist-model');
|
||||||
$this->kitsuModel = $this->container->get('kitsu-model');
|
$this->kitsuModel = $this->container->get('kitsu-model');
|
||||||
|
|
||||||
|
@ -27,12 +27,21 @@ use Aviat\Ion\View\HtmlView;
|
|||||||
* Controller for handling routes that don't fit elsewhere
|
* Controller for handling routes that don't fit elsewhere
|
||||||
*/
|
*/
|
||||||
final class Index extends BaseController {
|
final class Index extends BaseController {
|
||||||
|
/**
|
||||||
|
* @var \Aviat\API\Anilist\Model
|
||||||
|
*/
|
||||||
|
private $anilistModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Aviat\AnimeClient\Model\Settings
|
||||||
|
*/
|
||||||
private $settingsModel;
|
private $settingsModel;
|
||||||
|
|
||||||
public function __construct(ContainerInterface $container)
|
public function __construct(ContainerInterface $container)
|
||||||
{
|
{
|
||||||
parent::__construct($container);
|
parent::__construct($container);
|
||||||
|
|
||||||
|
$this->anilistModel = $container->get('anilist-model');
|
||||||
$this->settingsModel = $container->get('settings-model');
|
$this->settingsModel = $container->get('settings-model');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,10 +92,11 @@ final class Index extends BaseController {
|
|||||||
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
|
$redirectUrl = 'https://anilist.co/api/v2/oauth/authorize?' .
|
||||||
http_build_query([
|
http_build_query([
|
||||||
'client_id' => $this->config->get(['anilist', 'client_id']),
|
'client_id' => $this->config->get(['anilist', 'client_id']),
|
||||||
|
'redirect_uri' => $this->urlGenerator->url('/anilist-oauth'),
|
||||||
'response_type' => 'code',
|
'response_type' => 'code',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->redirect($redirectUrl, 301);
|
$this->redirect($redirectUrl, 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -94,10 +104,47 @@ final class Index extends BaseController {
|
|||||||
*/
|
*/
|
||||||
public function anilistCallback()
|
public function anilistCallback()
|
||||||
{
|
{
|
||||||
dump($_GET);
|
$query = $this->request->getQueryParams();
|
||||||
$this->outputHTML('blank', [
|
$authCode = $query['code'];
|
||||||
'title' => 'Oauth!'
|
$uri = $this->urlGenerator->url('/anilist-oauth');
|
||||||
]);
|
|
||||||
|
$authData = $this->anilistModel->authenticate($authCode, $uri);
|
||||||
|
$settings = $this->settingsModel->getSettings();
|
||||||
|
|
||||||
|
if (array_key_exists('error', $authData))
|
||||||
|
{
|
||||||
|
$this->errorPage(400, 'Error Linking Account', $authData['hint']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the override config file
|
||||||
|
$anilistSettings = [
|
||||||
|
'access_token' => $authData['access_token'],
|
||||||
|
'access_token_expires' => (time() - 10) + $authData['expires_in'],
|
||||||
|
'refresh_token' => $authData['refresh_token'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$newSettings = $settings;
|
||||||
|
$newSettings['anilist'] = array_merge($settings['anilist'], $anilistSettings);
|
||||||
|
|
||||||
|
foreach($newSettings['config'] as $key => $value)
|
||||||
|
{
|
||||||
|
$newSettings[$key] = $value;
|
||||||
|
}
|
||||||
|
unset($newSettings['config']);
|
||||||
|
|
||||||
|
$saved = $this->settingsModel->saveSettingsFile($newSettings);
|
||||||
|
|
||||||
|
if ($saved)
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Linked Anilist Account', 'success');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->setFlashMessage('Error Linking Anilist Account', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect($this->url->generate('settings'), 303);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,11 +212,13 @@ final class Index extends BaseController {
|
|||||||
$auth = $this->container->get('auth');
|
$auth = $this->container->get('auth');
|
||||||
$form = $this->settingsModel->getSettingsForm();
|
$form = $this->settingsModel->getSettingsForm();
|
||||||
|
|
||||||
// dump($this->session->getFlash('message'));
|
$hasAnilistLogin = $this->config->has(['anilist','access_token']);
|
||||||
|
|
||||||
$this->outputHTML('settings', [
|
$this->outputHTML('settings', [
|
||||||
|
'anilistModel' => $this->anilistModel,
|
||||||
'auth' => $auth,
|
'auth' => $auth,
|
||||||
'form' => $form,
|
'form' => $form,
|
||||||
|
'hasAnilistLogin' => $hasAnilistLogin,
|
||||||
'config' => $this->config,
|
'config' => $this->config,
|
||||||
'title' => $this->config->get('whose_list') . "'s Settings",
|
'title' => $this->config->get('whose_list') . "'s Settings",
|
||||||
]);
|
]);
|
||||||
@ -183,6 +232,7 @@ final class Index extends BaseController {
|
|||||||
public function settings_post()
|
public function settings_post()
|
||||||
{
|
{
|
||||||
$post = $this->request->getParsedBody();
|
$post = $this->request->getParsedBody();
|
||||||
|
unset($post['settings-tabs']);
|
||||||
|
|
||||||
// dump($post);
|
// dump($post);
|
||||||
$saved = $this->settingsModel->saveSettingsFile($post);
|
$saved = $this->settingsModel->saveSettingsFile($post);
|
||||||
|
@ -79,11 +79,6 @@ final class FormGenerator {
|
|||||||
switch($type)
|
switch($type)
|
||||||
{
|
{
|
||||||
case 'boolean':
|
case 'boolean':
|
||||||
/* $params['type'] = 'checkbox';
|
|
||||||
$params['attribs']['label'] = $form['description'];
|
|
||||||
$params['attribs']['value'] = TRUE;
|
|
||||||
$params['attribs']['value_unchecked'] = '0'; */
|
|
||||||
|
|
||||||
$params['type'] = 'radio';
|
$params['type'] = 'radio';
|
||||||
$params['options'] = [
|
$params['options'] = [
|
||||||
'1' => 'Yes',
|
'1' => 'Yes',
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
namespace Aviat\AnimeClient\Model;
|
namespace Aviat\AnimeClient\Model;
|
||||||
|
|
||||||
|
use const Aviat\AnimeClient\SETTINGS_MAP;
|
||||||
|
|
||||||
use function Aviat\AnimeClient\arrayToToml;
|
use function Aviat\AnimeClient\arrayToToml;
|
||||||
use function Aviat\Ion\_dir;
|
use function Aviat\Ion\_dir;
|
||||||
|
|
||||||
@ -34,177 +36,6 @@ final class Settings {
|
|||||||
|
|
||||||
private $config;
|
private $config;
|
||||||
|
|
||||||
/**
|
|
||||||
* Map the config values to types and form fields
|
|
||||||
*/
|
|
||||||
private const SETTINGS_MAP = [
|
|
||||||
'anilist' => [
|
|
||||||
'enabled' => [
|
|
||||||
'type' => 'boolean',
|
|
||||||
'title' => 'Enable Anilist Integration',
|
|
||||||
'default' => FALSE,
|
|
||||||
'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config',
|
|
||||||
],
|
|
||||||
'client_id' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Anilist API Client ID',
|
|
||||||
'default' => '',
|
|
||||||
'description' => 'The client id for your Anilist API application',
|
|
||||||
],
|
|
||||||
'client_secret' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Anilist API Client Secret',
|
|
||||||
'default' => '',
|
|
||||||
'description' => 'The client secret for your Anilist API application',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'config' => [
|
|
||||||
'kitsu_username' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Kitsu Username',
|
|
||||||
'default' => '',
|
|
||||||
'description' => 'Username of the account to pull list data from.',
|
|
||||||
],
|
|
||||||
'whose_list' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Whose List',
|
|
||||||
'default' => 'Somebody',
|
|
||||||
'description' => 'Name of the owner of the list data.',
|
|
||||||
],
|
|
||||||
'show_anime_collection' => [
|
|
||||||
'type' => 'boolean',
|
|
||||||
'title' => 'Show Anime Collection',
|
|
||||||
'default' => FALSE,
|
|
||||||
'description' => 'Should the anime collection be shown?',
|
|
||||||
],
|
|
||||||
'show_manga_collection' => [
|
|
||||||
'type' => 'boolean',
|
|
||||||
'title' => 'Show Manga Collection',
|
|
||||||
'default' => FALSE,
|
|
||||||
'description' => 'Should the manga collection be shown?',
|
|
||||||
],
|
|
||||||
'default_list' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'title' => 'Default List',
|
|
||||||
'description' => 'Which list to show by default.',
|
|
||||||
'options' => [
|
|
||||||
'Anime' => 'anime',
|
|
||||||
'Manga' => 'manga',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all
|
|
||||||
'type' => 'select',
|
|
||||||
'title' => 'Default Anime List Section',
|
|
||||||
'description' => 'Which part of the anime list to show by default.',
|
|
||||||
'options' => [
|
|
||||||
'Watching' => 'watching',
|
|
||||||
'Plan to Watch' => 'plan_to_watch',
|
|
||||||
'On Hold' => 'on_hold',
|
|
||||||
'Dropped' => 'dropped',
|
|
||||||
'Completed' => 'completed',
|
|
||||||
'All' => 'all',
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all
|
|
||||||
'type' => 'select',
|
|
||||||
'title' => 'Default Manga List Section',
|
|
||||||
'description' => 'Which part of the manga list to show by default.',
|
|
||||||
'options' => [
|
|
||||||
'Reading' => 'reading',
|
|
||||||
'Plan to Read' => 'plan_to_read',
|
|
||||||
'On Hold' => 'on_hold',
|
|
||||||
'Dropped' => 'dropped',
|
|
||||||
'Completed' => 'completed',
|
|
||||||
'All' => 'all',
|
|
||||||
]
|
|
||||||
]
|
|
||||||
],
|
|
||||||
'cache' => [
|
|
||||||
'driver' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'title' => 'Cache Type',
|
|
||||||
'description' => 'The Cache backend',
|
|
||||||
'options' => [
|
|
||||||
'APCu' => 'apcu',
|
|
||||||
'Memcached' => 'memcached',
|
|
||||||
'Redis' => 'redis',
|
|
||||||
'No Cache' => 'null'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'connection' => [
|
|
||||||
'type' => 'subfield',
|
|
||||||
'title' => 'Connection',
|
|
||||||
'fields' => [
|
|
||||||
'host' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Cache Host',
|
|
||||||
'description' => 'Host of the cache backend to connect to',
|
|
||||||
],
|
|
||||||
'port' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Cache Port',
|
|
||||||
'description' => 'Port of the cache backend to connect to',
|
|
||||||
'default' => NULL,
|
|
||||||
],
|
|
||||||
'password' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Cache Password',
|
|
||||||
'description' => 'Password to connect to cache backend',
|
|
||||||
'default' => NULL,
|
|
||||||
],
|
|
||||||
'database' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Cache Database',
|
|
||||||
'description' => 'Cache database number for Redis',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'database' => [
|
|
||||||
'type' => [
|
|
||||||
'type' => 'select',
|
|
||||||
'title' => 'Database Type',
|
|
||||||
'options' => [
|
|
||||||
'MySQL' => 'mysql',
|
|
||||||
'PostgreSQL' => 'pgsql',
|
|
||||||
'SQLite' => 'sqlite',
|
|
||||||
],
|
|
||||||
'description' => 'Type of database to connect to',
|
|
||||||
],
|
|
||||||
'host' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Host',
|
|
||||||
'description' => 'The host of the database server',
|
|
||||||
],
|
|
||||||
'user' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'User',
|
|
||||||
'description' => 'Database connection user',
|
|
||||||
],
|
|
||||||
'pass' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Password',
|
|
||||||
'description' => 'Database connection password'
|
|
||||||
],
|
|
||||||
'port' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Port',
|
|
||||||
'description' => 'Database connection port',
|
|
||||||
'default' => NULL,
|
|
||||||
],
|
|
||||||
'database' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Database Name',
|
|
||||||
'description' => 'Name of the database/schema to connect to',
|
|
||||||
],
|
|
||||||
'file' => [
|
|
||||||
'type' => 'string',
|
|
||||||
'title' => 'Database File',
|
|
||||||
'description' => 'Path to the database file, if required by the current database type.'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
public function __construct(ConfigInterface $config)
|
public function __construct(ConfigInterface $config)
|
||||||
{
|
{
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
@ -216,7 +47,7 @@ final class Settings {
|
|||||||
'config' => [],
|
'config' => [],
|
||||||
];
|
];
|
||||||
|
|
||||||
foreach(static::SETTINGS_MAP as $file => $values)
|
foreach(SETTINGS_MAP as $file => $values)
|
||||||
{
|
{
|
||||||
if ($file === 'config')
|
if ($file === 'config')
|
||||||
{
|
{
|
||||||
@ -243,7 +74,9 @@ final class Settings {
|
|||||||
|
|
||||||
foreach($settings as $file => $values)
|
foreach($settings as $file => $values)
|
||||||
{
|
{
|
||||||
foreach(static::SETTINGS_MAP[$file] as $key => $value)
|
$values = $values ?? [];
|
||||||
|
|
||||||
|
foreach(SETTINGS_MAP[$file] as $key => $value)
|
||||||
{
|
{
|
||||||
if ($value['type'] === 'subfield')
|
if ($value['type'] === 'subfield')
|
||||||
{
|
{
|
||||||
@ -317,7 +150,7 @@ final class Settings {
|
|||||||
$looseConfig[$key] = $val;
|
$looseConfig[$key] = $val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
elseif (is_array($val))
|
elseif (is_array($val) && ! empty($val))
|
||||||
{
|
{
|
||||||
foreach($val as $k => $v)
|
foreach($val as $k => $v)
|
||||||
{
|
{
|
||||||
@ -357,7 +190,11 @@ final class Settings {
|
|||||||
|
|
||||||
public function saveSettingsFile(array $settings): bool
|
public function saveSettingsFile(array $settings): bool
|
||||||
{
|
{
|
||||||
$settings = $settings['config'];
|
$configWrapped = (count(array_keys($settings)) === 1 && array_key_exists('config', $settings));
|
||||||
|
if ($configWrapped)
|
||||||
|
{
|
||||||
|
$settings = $settings['config'];
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -365,6 +202,9 @@ final class Settings {
|
|||||||
}
|
}
|
||||||
catch (UndefinedPropertyException $e)
|
catch (UndefinedPropertyException $e)
|
||||||
{
|
{
|
||||||
|
dump($e);
|
||||||
|
dump($settings);
|
||||||
|
die();
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,11 +23,10 @@ class Anilist extends AbstractType {
|
|||||||
|
|
||||||
public $client_id;
|
public $client_id;
|
||||||
public $client_secret;
|
public $client_secret;
|
||||||
public $redirect_uri;
|
|
||||||
|
|
||||||
public $access_token;
|
public $access_token;
|
||||||
|
public $access_token_expires;
|
||||||
public $refresh_token;
|
public $refresh_token;
|
||||||
|
|
||||||
public $user_id;
|
|
||||||
public $username;
|
public $username;
|
||||||
}
|
}
|
@ -88,7 +88,9 @@ class UrlGenerator extends RoutingBase {
|
|||||||
}
|
}
|
||||||
$path = implode('/', $path_segments);
|
$path = implode('/', $path_segments);
|
||||||
|
|
||||||
return "//{$this->host}/{$path}";
|
$scheme = ($_SERVER['HTTPS'] === 'on') ? 'https:' : '';
|
||||||
|
|
||||||
|
return "{$scheme}//{$this->host}/{$path}";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -27,4 +27,218 @@ const SRC_DIR = __DIR__;
|
|||||||
const USER_AGENT = "Tim's Anime Client/4.0";
|
const USER_AGENT = "Tim's Anime Client/4.0";
|
||||||
|
|
||||||
// Why doesn't this already exist?
|
// Why doesn't this already exist?
|
||||||
const MILLI_FROM_NANO = 1000 * 1000;
|
const MILLI_FROM_NANO = 1000 * 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map config settings to form fields
|
||||||
|
*/
|
||||||
|
const SETTINGS_MAP = [
|
||||||
|
'anilist' => [
|
||||||
|
'enabled' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'title' => 'Enable Anilist Integration',
|
||||||
|
'default' => FALSE,
|
||||||
|
'description' => 'Enable syncing data between Kitsu and Anilist. Requires appropriate API keys to be set in config',
|
||||||
|
],
|
||||||
|
'client_id' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Anilist API Client ID',
|
||||||
|
'default' => '',
|
||||||
|
'description' => 'The client id for your Anilist API application',
|
||||||
|
],
|
||||||
|
'client_secret' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Anilist API Client Secret',
|
||||||
|
'default' => '',
|
||||||
|
'description' => 'The client secret for your Anilist API application',
|
||||||
|
],
|
||||||
|
'username' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Anilist Username',
|
||||||
|
'default' => '',
|
||||||
|
'readonly' => TRUE,
|
||||||
|
'description' => 'Login username for Anilist account to integrate with',
|
||||||
|
],
|
||||||
|
'access_token' => [
|
||||||
|
'type' => 'hidden',
|
||||||
|
'title' => 'API Access Token',
|
||||||
|
'default' => '',
|
||||||
|
'description' => 'The Access code for accessing the Anilist API',
|
||||||
|
'readonly' => TRUE,
|
||||||
|
],
|
||||||
|
'access_token_expires' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Expiration timestamp of the access token',
|
||||||
|
'default' => '0',
|
||||||
|
'description' => 'The unix timestamp of when the access token expires.',
|
||||||
|
'readonly' => TRUE,
|
||||||
|
],
|
||||||
|
'refresh_token' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'API Refresh Token',
|
||||||
|
'default' => '',
|
||||||
|
'description' => 'Token to refresh the access token before it expires',
|
||||||
|
'readonly' => TRUE,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
'driver' => [
|
||||||
|
'type' => 'select',
|
||||||
|
'title' => 'Cache Type',
|
||||||
|
'description' => 'The Cache backend',
|
||||||
|
'options' => [
|
||||||
|
'APCu' => 'apcu',
|
||||||
|
'Memcached' => 'memcached',
|
||||||
|
'Redis' => 'redis',
|
||||||
|
'No Cache' => 'null'
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'connection' => [
|
||||||
|
'type' => 'subfield',
|
||||||
|
'title' => 'Connection',
|
||||||
|
'fields' => [
|
||||||
|
'host' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Cache Host',
|
||||||
|
'description' => 'Host of the cache backend to connect to',
|
||||||
|
],
|
||||||
|
'port' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Cache Port',
|
||||||
|
'description' => 'Port of the cache backend to connect to',
|
||||||
|
'default' => NULL,
|
||||||
|
],
|
||||||
|
'password' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Cache Password',
|
||||||
|
'description' => 'Password to connect to cache backend',
|
||||||
|
'default' => NULL,
|
||||||
|
],
|
||||||
|
'persistent' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'title' => 'Persistent Cache Connection',
|
||||||
|
'description' => 'Whether to have a persistent connection to the cache',
|
||||||
|
'default' => FALSE,
|
||||||
|
],
|
||||||
|
'database' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Cache Database',
|
||||||
|
'default' => '1',
|
||||||
|
'description' => 'Cache database number for Redis',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
/*'options' => [
|
||||||
|
'type' => 'subfield',
|
||||||
|
'title' => 'Options',
|
||||||
|
'fields' => [],
|
||||||
|
] */
|
||||||
|
],
|
||||||
|
'config' => [
|
||||||
|
'kitsu_username' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Kitsu Username',
|
||||||
|
'default' => '',
|
||||||
|
'description' => 'Username of the account to pull list data from.',
|
||||||
|
],
|
||||||
|
'whose_list' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Whose List',
|
||||||
|
'default' => 'Somebody',
|
||||||
|
'description' => 'Name of the owner of the list data.',
|
||||||
|
],
|
||||||
|
'show_anime_collection' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'title' => 'Show Anime Collection',
|
||||||
|
'default' => FALSE,
|
||||||
|
'description' => 'Should the anime collection be shown?',
|
||||||
|
],
|
||||||
|
'show_manga_collection' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'title' => 'Show Manga Collection',
|
||||||
|
'default' => FALSE,
|
||||||
|
'description' => 'Should the manga collection be shown?',
|
||||||
|
],
|
||||||
|
'default_list' => [
|
||||||
|
'type' => 'select',
|
||||||
|
'title' => 'Default List',
|
||||||
|
'description' => 'Which list to show by default.',
|
||||||
|
'options' => [
|
||||||
|
'Anime' => 'anime',
|
||||||
|
'Manga' => 'manga',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'default_anime_list_path' => [ //watching|plan_to_watch|on_hold|dropped|completed|all
|
||||||
|
'type' => 'select',
|
||||||
|
'title' => 'Default Anime List Section',
|
||||||
|
'description' => 'Which part of the anime list to show by default.',
|
||||||
|
'options' => [
|
||||||
|
'Watching' => 'watching',
|
||||||
|
'Plan to Watch' => 'plan_to_watch',
|
||||||
|
'On Hold' => 'on_hold',
|
||||||
|
'Dropped' => 'dropped',
|
||||||
|
'Completed' => 'completed',
|
||||||
|
'All' => 'all',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'default_manga_list_path' => [ //reading|plan_to_read|on_hold|dropped|completed|all
|
||||||
|
'type' => 'select',
|
||||||
|
'title' => 'Default Manga List Section',
|
||||||
|
'description' => 'Which part of the manga list to show by default.',
|
||||||
|
'options' => [
|
||||||
|
'Reading' => 'reading',
|
||||||
|
'Plan to Read' => 'plan_to_read',
|
||||||
|
'On Hold' => 'on_hold',
|
||||||
|
'Dropped' => 'dropped',
|
||||||
|
'Completed' => 'completed',
|
||||||
|
'All' => 'all',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'database' => [
|
||||||
|
'type' => [
|
||||||
|
'type' => 'select',
|
||||||
|
'title' => 'Database Type',
|
||||||
|
'options' => [
|
||||||
|
'MySQL' => 'mysql',
|
||||||
|
'PostgreSQL' => 'pgsql',
|
||||||
|
'SQLite' => 'sqlite',
|
||||||
|
],
|
||||||
|
'default' => 'sqlite',
|
||||||
|
'description' => 'Type of database to connect to',
|
||||||
|
],
|
||||||
|
'host' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Host',
|
||||||
|
'description' => 'The host of the database server',
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'User',
|
||||||
|
'description' => 'Database connection user',
|
||||||
|
],
|
||||||
|
'pass' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Password',
|
||||||
|
'description' => 'Database connection password'
|
||||||
|
],
|
||||||
|
'port' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Port',
|
||||||
|
'description' => 'Database connection port',
|
||||||
|
'default' => NULL,
|
||||||
|
],
|
||||||
|
'database' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Database Name',
|
||||||
|
'description' => 'Name of the database/schema to connect to',
|
||||||
|
],
|
||||||
|
'file' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'title' => 'Database File',
|
||||||
|
'description' => 'Path to the database file, if required by the current database type.',
|
||||||
|
'default' => 'anime_collection.sqlite',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
Loading…
Reference in New Issue
Block a user