Merge pull request #857 from ArthurHoaro/api/history-get
API: Get History endpoint
This commit is contained in:
commit
a4af59f471
4 changed files with 375 additions and 0 deletions
71
application/api/controllers/History.php
Normal file
71
application/api/controllers/History.php
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Shaarli\Api\Controllers;
|
||||||
|
|
||||||
|
use Shaarli\Api\Exceptions\ApiBadParametersException;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class History
|
||||||
|
*
|
||||||
|
* REST API Controller: /history
|
||||||
|
*
|
||||||
|
* @package Shaarli\Api\Controllers
|
||||||
|
*/
|
||||||
|
class History extends ApiController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Service providing operation regarding Shaarli datastore and settings.
|
||||||
|
*
|
||||||
|
* @param Request $request Slim request.
|
||||||
|
* @param Response $response Slim response.
|
||||||
|
*
|
||||||
|
* @return Response response.
|
||||||
|
*
|
||||||
|
* @throws ApiBadParametersException Invalid parameters.
|
||||||
|
*/
|
||||||
|
public function getHistory($request, $response)
|
||||||
|
{
|
||||||
|
$history = (new \History($this->conf->get('resource.history')))->getHistory();
|
||||||
|
$history = array_reverse($history);
|
||||||
|
|
||||||
|
// Return history operations from the {offset}th, starting from {since}.
|
||||||
|
$since = \DateTime::createFromFormat(\DateTime::ATOM, $request->getParam('since'));
|
||||||
|
$offset = $request->getParam('offset');
|
||||||
|
if (empty($offset)) {
|
||||||
|
$offset = 0;
|
||||||
|
}
|
||||||
|
else if (ctype_digit($offset)) {
|
||||||
|
$offset = (int) $offset;
|
||||||
|
} else {
|
||||||
|
throw new ApiBadParametersException('Invalid offset');
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit parameter is either a number of links or 'all' for everything.
|
||||||
|
$limit = $request->getParam('limit');
|
||||||
|
if (empty($limit)) {
|
||||||
|
$limit = count($history);
|
||||||
|
} else if (ctype_digit($limit)) {
|
||||||
|
$limit = (int) $limit;
|
||||||
|
} else {
|
||||||
|
throw new ApiBadParametersException('Invalid limit');
|
||||||
|
}
|
||||||
|
|
||||||
|
$out = [];
|
||||||
|
$i = 0;
|
||||||
|
foreach ($history as $entry) {
|
||||||
|
if ((! empty($since) && $entry['datetime'] <= $since) || count($out) >= $limit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (++$i > $offset) {
|
||||||
|
$out[$i] = $entry;
|
||||||
|
$out[$i]['datetime'] = $out[$i]['datetime']->format(\DateTime::ATOM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$out = array_values($out);
|
||||||
|
|
||||||
|
return $response->withJson($out, 200, $this->jsonStyle);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2253,6 +2253,7 @@ function resizeImage($filepath)
|
||||||
$this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
|
$this->post('/links', '\Shaarli\Api\Controllers\Links:postLink')->setName('postLink');
|
||||||
$this->put('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:putLink')->setName('putLink');
|
$this->put('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:putLink')->setName('putLink');
|
||||||
$this->delete('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:deleteLink')->setName('deleteLink');
|
$this->delete('/links/{id:[\d]+}', '\Shaarli\Api\Controllers\Links:deleteLink')->setName('deleteLink');
|
||||||
|
$this->get('/history', '\Shaarli\Api\Controllers\History:getHistory')->setName('getHistory');
|
||||||
})->add('\Shaarli\Api\ApiMiddleware');
|
})->add('\Shaarli\Api\ApiMiddleware');
|
||||||
|
|
||||||
$response = $app->run(true);
|
$response = $app->run(true);
|
||||||
|
|
221
tests/api/controllers/HistoryTest.php
Normal file
221
tests/api/controllers/HistoryTest.php
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
namespace Shaarli\Api\Controllers;
|
||||||
|
|
||||||
|
|
||||||
|
use Shaarli\Config\ConfigManager;
|
||||||
|
use Slim\Container;
|
||||||
|
use Slim\Http\Environment;
|
||||||
|
use Slim\Http\Request;
|
||||||
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
require_once 'tests/utils/ReferenceHistory.php';
|
||||||
|
|
||||||
|
class HistoryTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string datastore to test write operations
|
||||||
|
*/
|
||||||
|
protected static $testHistory = 'sandbox/history.php';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ConfigManager instance
|
||||||
|
*/
|
||||||
|
protected $conf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \ReferenceHistory instance.
|
||||||
|
*/
|
||||||
|
protected $refHistory = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \History instance.
|
||||||
|
*/
|
||||||
|
protected $history;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Container instance.
|
||||||
|
*/
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var History controller instance.
|
||||||
|
*/
|
||||||
|
protected $controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Before every test, instantiate a new Api with its config, plugins and links.
|
||||||
|
*/
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
$this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
|
||||||
|
$this->refHistory = new \ReferenceHistory();
|
||||||
|
$this->refHistory->write(self::$testHistory);
|
||||||
|
$this->conf->set('resource.history', self::$testHistory);
|
||||||
|
$this->container = new Container();
|
||||||
|
$this->container['conf'] = $this->conf;
|
||||||
|
$this->container['db'] = true;
|
||||||
|
|
||||||
|
$this->controller = new History($this->container);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After every test, remove the test datastore.
|
||||||
|
*/
|
||||||
|
public function tearDown()
|
||||||
|
{
|
||||||
|
@unlink(self::$testHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test /history service without parameter.
|
||||||
|
*/
|
||||||
|
public function testGetHistory()
|
||||||
|
{
|
||||||
|
$env = Environment::mock([
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
]);
|
||||||
|
$request = Request::createFromEnvironment($env);
|
||||||
|
|
||||||
|
$response = $this->controller->getHistory($request, new Response());
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
$this->assertEquals($this->refHistory->count(), count($data));
|
||||||
|
|
||||||
|
$this->assertEquals(\History::DELETED, $data[0]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
|
||||||
|
$data[0]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(124, $data[0]['id']);
|
||||||
|
|
||||||
|
$this->assertEquals(\History::SETTINGS, $data[1]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
|
||||||
|
$data[1]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertNull($data[1]['id']);
|
||||||
|
|
||||||
|
$this->assertEquals(\History::UPDATED, $data[2]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170301_121214')->format(\DateTime::ATOM),
|
||||||
|
$data[2]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(123, $data[2]['id']);
|
||||||
|
|
||||||
|
$this->assertEquals(\History::CREATED, $data[3]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170201_121214')->format(\DateTime::ATOM),
|
||||||
|
$data[3]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(124, $data[3]['id']);
|
||||||
|
|
||||||
|
$this->assertEquals(\History::CREATED, $data[4]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
|
||||||
|
$data[4]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(123, $data[4]['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test /history service with limit parameter.
|
||||||
|
*/
|
||||||
|
public function testGetHistoryLimit()
|
||||||
|
{
|
||||||
|
$env = Environment::mock([
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
'QUERY_STRING' => 'limit=1'
|
||||||
|
]);
|
||||||
|
$request = Request::createFromEnvironment($env);
|
||||||
|
|
||||||
|
$response = $this->controller->getHistory($request, new Response());
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($data));
|
||||||
|
|
||||||
|
$this->assertEquals(\History::DELETED, $data[0]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
|
||||||
|
$data[0]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(124, $data[0]['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test /history service with offset parameter.
|
||||||
|
*/
|
||||||
|
public function testGetHistoryOffset()
|
||||||
|
{
|
||||||
|
$env = Environment::mock([
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
'QUERY_STRING' => 'offset=4'
|
||||||
|
]);
|
||||||
|
$request = Request::createFromEnvironment($env);
|
||||||
|
|
||||||
|
$response = $this->controller->getHistory($request, new Response());
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($data));
|
||||||
|
|
||||||
|
$this->assertEquals(\History::CREATED, $data[0]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
|
||||||
|
$data[0]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(123, $data[0]['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test /history service with since parameter.
|
||||||
|
*/
|
||||||
|
public function testGetHistorySince()
|
||||||
|
{
|
||||||
|
$env = Environment::mock([
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
'QUERY_STRING' => 'since=2017-03-03T00:00:00%2B00:00'
|
||||||
|
]);
|
||||||
|
$request = Request::createFromEnvironment($env);
|
||||||
|
|
||||||
|
$response = $this->controller->getHistory($request, new Response());
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($data));
|
||||||
|
|
||||||
|
$this->assertEquals(\History::DELETED, $data[0]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
|
||||||
|
$data[0]['datetime']
|
||||||
|
);
|
||||||
|
$this->assertEquals(124, $data[0]['id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test /history service with since parameter.
|
||||||
|
*/
|
||||||
|
public function testGetHistorySinceOffsetLimit()
|
||||||
|
{
|
||||||
|
$env = Environment::mock([
|
||||||
|
'REQUEST_METHOD' => 'GET',
|
||||||
|
'QUERY_STRING' => 'since=2017-02-01T00:00:00%2B00:00&offset=1&limit=1'
|
||||||
|
]);
|
||||||
|
$request = Request::createFromEnvironment($env);
|
||||||
|
|
||||||
|
$response = $this->controller->getHistory($request, new Response());
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$data = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
$this->assertEquals(1, count($data));
|
||||||
|
|
||||||
|
$this->assertEquals(\History::SETTINGS, $data[0]['event']);
|
||||||
|
$this->assertEquals(
|
||||||
|
\DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
|
||||||
|
$data[0]['datetime']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
82
tests/utils/ReferenceHistory.php
Normal file
82
tests/utils/ReferenceHistory.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates a reference history
|
||||||
|
*/
|
||||||
|
class ReferenceHistory
|
||||||
|
{
|
||||||
|
private $count;
|
||||||
|
|
||||||
|
private $history = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the test DB with reference data
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->addEntry(
|
||||||
|
History::CREATED,
|
||||||
|
DateTime::createFromFormat('Ymd_His', '20170101_121212'),
|
||||||
|
123
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addEntry(
|
||||||
|
History::CREATED,
|
||||||
|
DateTime::createFromFormat('Ymd_His', '20170201_121214'),
|
||||||
|
124
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addEntry(
|
||||||
|
History::UPDATED,
|
||||||
|
DateTime::createFromFormat('Ymd_His', '20170301_121214'),
|
||||||
|
123
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addEntry(
|
||||||
|
History::SETTINGS,
|
||||||
|
DateTime::createFromFormat('Ymd_His', '20170302_121215')
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->addEntry(
|
||||||
|
History::DELETED,
|
||||||
|
DateTime::createFromFormat('Ymd_His', '20170303_121216'),
|
||||||
|
124
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new history entry
|
||||||
|
*
|
||||||
|
* @param string $event Event identifier
|
||||||
|
* @param DateTime $datetime creation date
|
||||||
|
* @param int $id optional: related link ID
|
||||||
|
*/
|
||||||
|
protected function addEntry($event, $datetime, $id = null)
|
||||||
|
{
|
||||||
|
$link = [
|
||||||
|
'event' => $event,
|
||||||
|
'datetime' => $datetime,
|
||||||
|
'id' => $id,
|
||||||
|
];
|
||||||
|
$this->history[] = $link;
|
||||||
|
$this->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to the datastore
|
||||||
|
*
|
||||||
|
* @param string $filename write history content to.
|
||||||
|
*/
|
||||||
|
public function write($filename)
|
||||||
|
{
|
||||||
|
FileUtils::writeFlatDB($filename, $this->history);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of links in the reference data
|
||||||
|
*/
|
||||||
|
public function count()
|
||||||
|
{
|
||||||
|
return $this->count;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue