Added API documentation prototype

This commit is contained in:
Marcin Kurczewski 2014-05-17 11:45:27 +02:00
parent 03a6809510
commit 634d0061d4
12 changed files with 205 additions and 24 deletions

View file

@ -0,0 +1,5 @@
pre {
background: ghostwhite;
padding: 0.5em;
border-left: 0.2em solid silver;
}

View file

@ -1,6 +1,11 @@
<?php <?php
final class Api final class Api
{ {
public static function getUrl()
{
return \Chibi\Router::linkTo(['ApiController', 'runAction']);
}
public static function run(IJob $job, $jobArgs) public static function run(IJob $job, $jobArgs)
{ {
$user = Auth::getCurrentUser(); $user = Auth::getCurrentUser();

View file

@ -59,4 +59,14 @@ class JobArgs
{ {
return JobArgsOptional::factory(func_get_args()); return JobArgsOptional::factory(func_get_args());
} }
public static function getInternalArguments()
{
return
[
self::ARG_POST_ENTITY,
self::ARG_USER_ENTITY,
self::ARG_COMMENT_ENTITY
];
}
} }

View file

@ -7,6 +7,7 @@ abstract class AbstractJob implements IJob
protected $arguments = []; protected $arguments = [];
protected $context = self::CONTEXT_NORMAL; protected $context = self::CONTEXT_NORMAL;
protected $subJobs;
public function prepare() public function prepare()
{ {
@ -27,6 +28,16 @@ abstract class AbstractJob implements IJob
return $name; return $name;
} }
public function addSubJob(IJob $subJob)
{
$this->subJobs []= $subJob;
}
public function getSubJobs()
{
return $this->subJobs;
}
public function getRequiredPrivileges() public function getRequiredPrivileges()
{ {
return false; return false;

View file

@ -1,6 +1,11 @@
<?php <?php
class AddPostJob extends AbstractJob class AddPostJob extends AbstractJob
{ {
public function __construct()
{
$this->addSubJob(new EditPostJob());
}
public function execute() public function execute()
{ {
$post = PostModel::spawn(); $post = PostModel::spawn();
@ -20,7 +25,7 @@ class AddPostJob extends AbstractJob
Logger::bufferChanges(); Logger::bufferChanges();
try try
{ {
$job = new EditPostJob(); $job = $this->getSubJobs()[0];
$job->setContext(AbstractJob::CONTEXT_BATCH_ADD); $job->setContext(AbstractJob::CONTEXT_BATCH_ADD);
Api::run($job, $arguments); Api::run($job, $arguments);
} }

View file

@ -6,6 +6,12 @@ class EditPostJob extends AbstractJob
public function __construct() public function __construct()
{ {
$this->postRetriever = new PostRetriever($this); $this->postRetriever = new PostRetriever($this);
$this->addSubJob(new EditPostSafetyJob());
$this->addSubJob(new EditPostTagsJob());
$this->addSubJob(new EditPostSourceJob());
$this->addSubJob(new EditPostRelationsJob());
$this->addSubJob(new EditPostContentJob());
$this->addSubJob(new EditPostThumbJob());
} }
public function execute() public function execute()
@ -14,17 +20,7 @@ class EditPostJob extends AbstractJob
Logger::bufferChanges(); Logger::bufferChanges();
$subJobs = foreach ($this->getSubJobs() as $subJob)
[
new EditPostSafetyJob(),
new EditPostTagsJob(),
new EditPostSourceJob(),
new EditPostRelationsJob(),
new EditPostContentJob(),
new EditPostThumbJob(),
];
foreach ($subJobs as $subJob)
{ {
$subJob->setContext($this->getContext() == self::CONTEXT_BATCH_ADD $subJob->setContext($this->getContext() == self::CONTEXT_BATCH_ADD
? self::CONTEXT_BATCH_ADD ? self::CONTEXT_BATCH_ADD

View file

@ -1,6 +1,11 @@
<?php <?php
class AddUserJob extends AbstractJob class AddUserJob extends AbstractJob
{ {
public function __construct()
{
$this->addSubJob(new EditUserJob());
}
public function execute() public function execute()
{ {
$firstUser = UserModel::getCount() == 0; $firstUser = UserModel::getCount() == 0;
@ -25,7 +30,7 @@ class AddUserJob extends AbstractJob
Logger::bufferChanges(); Logger::bufferChanges();
try try
{ {
$job = new EditUserJob(); $job = $this->getSubJobs()[0];
$job->setContext(self::CONTEXT_BATCH_ADD); $job->setContext(self::CONTEXT_BATCH_ADD);
Api::run($job, $arguments); Api::run($job, $arguments);
} }

View file

@ -2,24 +2,20 @@
class EditUserJob extends AbstractJob class EditUserJob extends AbstractJob
{ {
protected $userRetriever; protected $userRetriever;
protected $subJobs;
public function __construct() public function __construct()
{ {
$this->userRetriever = new UserRetriever($this); $this->userRetriever = new UserRetriever($this);
$this->subJobs = $this->addSubJob(new EditUserAccessRankJob());
[ $this->addSubJob(new EditUserNameJob());
new EditUserAccessRankJob(), $this->addSubJob(new EditUserPasswordJob());
new EditUserNameJob(), $this->addSubJob(new EditUserEmailJob());
new EditUserPasswordJob(),
new EditUserEmailJob(),
];
} }
public function canEditAnything($user) public function canEditAnything($user)
{ {
$this->privileges = []; $this->privileges = [];
foreach ($this->subJobs as $subJob) foreach ($this->getSubJobs() as $subJob)
{ {
try try
{ {
@ -40,7 +36,7 @@ class EditUserJob extends AbstractJob
Logger::bufferChanges(); Logger::bufferChanges();
foreach ($this->subJobs as $subJob) foreach ($this->getSubJobs() as $subJob)
{ {
$subJob->setContext($this->getContext() == self::CONTEXT_BATCH_ADD $subJob->setContext($this->getContext() == self::CONTEXT_BATCH_ADD
? self::CONTEXT_BATCH_ADD ? self::CONTEXT_BATCH_ADD

View file

@ -38,6 +38,11 @@ class StaticPagesController extends AbstractController
$this->renderView('static-help'); $this->renderView('static-help');
} }
public function apiDocsView()
{
$this->renderView('static-api');
}
public function fatalErrorView($code = null) public function fatalErrorView($code = null)
{ {
throw new SimpleException('Error ' . $code . ' while retrieving ' . $_SERVER['REQUEST_URI']); throw new SimpleException('Error ' . $code . ' while retrieving ' . $_SERVER['REQUEST_URI']);

View file

@ -42,9 +42,10 @@ $this->assets->addScript('core.js');
<div class="left"> <div class="left">
<span> <span>
<a href="<?= Core::getConfig()->misc->githubLink ?>"> <a href="<?= Core::getConfig()->misc->githubLink ?>">
szurubooru&nbsp;<?= PropertyModel::get(PropertyModel::EngineVersion) ?> <?= PropertyModel::get(PropertyModel::EngineVersion) ?>
</a> </a>
</span> </span>
<span><a href="<?= \Chibi\Router::linkTo(['StaticPagesController', 'apiDocsView']) ?>">API</a></span>
<?php if (Access::check(new Privilege(Privilege::ListLogs))): ?> <?php if (Access::check(new Privilege(Privilege::ListLogs))): ?>
<span><a href="<?= \Chibi\Router::linkTo(['LogController', 'listView']) ?>">Logs</a></span> <span><a href="<?= \Chibi\Router::linkTo(['LogController', 'listView']) ?>">Logs</a></span>
<?php endif ?> <?php endif ?>

View file

@ -0,0 +1,141 @@
<?php
$this->assets->addStylesheet('static-api.css');
?>
<h2>API documentation</h2>
<p>All interaction with API proceeds through just one URL: <code><?= Api::getUrl() ?></code>.</p>
<p>One request to API means executing one so-called &bdquo;job&rdquo;. Most of things that can be done through web
interface, can be also done through the API.</p>
<hr/>
<h2>Request construction</h2>
<p>To specify job to be executed, send <code>name</code> parameter. Optional job arguments can be specified with
<code>args</code> parameter. Example using <code>curl</code>:</p>
<pre><code>curl \
--data 'name=get-post' \
'&amp;args[post-id]=5408' \
<?= Api::getUrl() ?></code></pre>
<h3>Authentication</h3>
<p>In order to make authenticated request, you need to supply request with your credentials. This can be done by
passing <code>auth[user]</code> and <code>auth[name]</code> parameters:</p>
<pre><code>curl \
--data 'auth[user]=example' \
'&amp;auth[pass]=secret' \
'&amp;name=get-post' \
'&amp;args[post-id]=5408' \
<?= Api::getUrl() ?></code></pre>
<h3>Sending files</h3>
<p>In order to send files to API, you have to send <code>multipart/form-data</code> request. With <code>curl</code>
this can be done using <code>-F</code> option to encode each parameter and prepending <code>@</code> character to
chosen file name:</p>
<pre><code>curl \
-F 'auth[user]=example' \
-F 'auth[pass]=secret' \
-F 'name=add-post' \
-F 'args[new-tag-names][0]=test' \
-F 'args[new-tag-names][1]=test2' \
-F 'args[new-tag-names][2]=test3' \
-F 'args[new-post-content]=@./custom.png' \
<?= Api::getUrl() ?></code></pre>
<h3>Output</h3>
<p>Output is always represented in JSON array. Files are stored in JSON as well using gzip compression and base64
encoding.</p>
<h3>Handling errors</h3>
<p>When errors occur all errors are logged to <code>message</code> field and changes done with request is rolled
back.</p>
<hr/>
<h2>Privilege table</h2>
<p>Each job checks for some privileges depending on context it is run with.</p>
<table>
<thead>
<tr>
<th>Privilege</th>
<th>Minimum access rank</th>
</tr>
</thead>
<tbody>
<?php foreach (Core::getConfig()->privileges as $privilege => $minAccessRank): ?>
<tr>
<td><?= $privilege ?></td>
<td><?= $minAccessRank ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<hr/>
<h2>Full list of available jobs</h2>
<?php
$jobClassNames = Api::getAllJobClassNames();
natcasesort($jobClassNames);
foreach ($jobClassNames as $className)
{
$job = new $className;
?>
<h3 id="job-<?= $job->getName() ?>">
<a href="#job-<?= $job->getName() ?>"><?= $job->getName() ?></a>
</h3>
<?php
$showArgs = function($args) use (&$showArgs)
{
if ($args instanceof JobArgsConjunction)
{
return '(' . implode(' AND ', array_filter(array_map(function($arg) use ($showArgs)
{
return $showArgs($arg);
}, $args->args))) . ')';
}
elseif ($args instanceof JobArgsAlternative)
{
return '(' . implode(' OR ', array_filter(array_map(function($arg) use ($showArgs)
{
return $showArgs($arg);
}, $args->args))) . ')';
}
elseif ($args instanceof JobArgsOptional)
return $showArgs($args->args[0]) . ' (optional)';
elseif (in_array($args, JobArgs::getInternalArguments()))
return null;
else
return $args;
};
?>
<p>Required arguments: <?= $showArgs($job->getRequiredArguments()) ?></p>
<p>Requires e-mail confirmation: <?= $job->isConfirmedEmailRequired() ? 'yes' : 'no' ?></p>
<p>Requires authentication: <?= $job->isAuthenticationRequired() ? 'yes' : 'no' ?></p>
<?php if (!empty($job->getSubJobs())): ?>
<p>Sub jobs: <?= implode(', ', array_map(function($job)
{
return '<a href="#job-' . $job->getName() . '">' . $job->getName() . '</a>';
}, $job->getSubJobs())); ?></p>
<?php endif ?>
<hr/>
<?php
}
?>

View file

@ -3,6 +3,7 @@
\Chibi\Router::register(['StaticPagesController', 'mainPageView'], 'GET', ''); \Chibi\Router::register(['StaticPagesController', 'mainPageView'], 'GET', '');
\Chibi\Router::register(['StaticPagesController', 'mainPageView'], 'GET', '/index'); \Chibi\Router::register(['StaticPagesController', 'mainPageView'], 'GET', '/index');
\Chibi\Router::register(['StaticPagesController', 'apiDocsView'], 'GET', '/api-docs');
\Chibi\Router::register(['StaticPagesController', 'helpView'], 'GET', '/help'); \Chibi\Router::register(['StaticPagesController', 'helpView'], 'GET', '/help');
\Chibi\Router::register(['StaticPagesController', 'helpView'], 'GET', '/help/{tab}'); \Chibi\Router::register(['StaticPagesController', 'helpView'], 'GET', '/help/{tab}');
\Chibi\Router::register(['StaticPagesController', 'fatalErrorView'], 'POST', '/fatal-error/{code}'); \Chibi\Router::register(['StaticPagesController', 'fatalErrorView'], 'POST', '/fatal-error/{code}');