Apply the new system (Bookmark + Service) to the whole code base

See https://github.com/shaarli/Shaarli/issues/1307
This commit is contained in:
ArthurHoaro 2019-05-25 15:52:27 +02:00
parent 336a28fa4a
commit cf92b4dd15
31 changed files with 634 additions and 1505 deletions

View file

@ -3,6 +3,7 @@ namespace Shaarli\Api;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
use Shaarli\Api\Exceptions\ApiException;
use Shaarli\Bookmark\BookmarkFileService;
use Shaarli\Config\ConfigManager;
use Slim\Container;
use Slim\Http\Request;
@ -117,7 +118,7 @@ class ApiMiddleware
}
/**
* Instantiate a new LinkDB including private links,
* Instantiate a new LinkDB including private bookmarks,
* and load in the Slim container.
*
* FIXME! LinkDB could use a refactoring to avoid this trick.
@ -126,10 +127,10 @@ class ApiMiddleware
*/
protected function setLinkDb($conf)
{
$linkDb = new \Shaarli\Bookmark\LinkDB(
$conf->get('resource.datastore'),
true,
$conf->get('privacy.hide_public_links')
$linkDb = new BookmarkFileService(
$conf,
$this->container->get('history'),
true
);
$this->container['db'] = $linkDb;
}

View file

@ -2,6 +2,7 @@
namespace Shaarli\Api;
use Shaarli\Api\Exceptions\ApiAuthorizationException;
use Shaarli\Bookmark\Bookmark;
use Shaarli\Http\Base64Url;
/**
@ -54,28 +55,28 @@ class ApiUtils
/**
* Format a Link for the REST API.
*
* @param array $link Link data read from the datastore.
* @param string $indexUrl Shaarli's index URL (used for relative URL).
* @param Bookmark $bookmark Bookmark data read from the datastore.
* @param string $indexUrl Shaarli's index URL (used for relative URL).
*
* @return array Link data formatted for the REST API.
*/
public static function formatLink($link, $indexUrl)
public static function formatLink($bookmark, $indexUrl)
{
$out['id'] = $link['id'];
$out['id'] = $bookmark->getId();
// Not an internal link
if (! is_note($link['url'])) {
$out['url'] = $link['url'];
if (! $bookmark->isNote()) {
$out['url'] = $bookmark->getUrl();
} else {
$out['url'] = $indexUrl . $link['url'];
$out['url'] = $indexUrl . $bookmark->getUrl();
}
$out['shorturl'] = $link['shorturl'];
$out['title'] = $link['title'];
$out['description'] = $link['description'];
$out['tags'] = preg_split('/\s+/', $link['tags'], -1, PREG_SPLIT_NO_EMPTY);
$out['private'] = $link['private'] == true;
$out['created'] = $link['created']->format(\DateTime::ATOM);
if (! empty($link['updated'])) {
$out['updated'] = $link['updated']->format(\DateTime::ATOM);
$out['shorturl'] = $bookmark->getShortUrl();
$out['title'] = $bookmark->getTitle();
$out['description'] = $bookmark->getDescription();
$out['tags'] = $bookmark->getTags();
$out['private'] = $bookmark->isPrivate();
$out['created'] = $bookmark->getCreated()->format(\DateTime::ATOM);
if (! empty($bookmark->getUpdated())) {
$out['updated'] = $bookmark->getUpdated()->format(\DateTime::ATOM);
} else {
$out['updated'] = '';
}
@ -83,7 +84,7 @@ class ApiUtils
}
/**
* Convert a link given through a request, to a valid link for LinkDB.
* Convert a link given through a request, to a valid Bookmark for the datastore.
*
* If no URL is provided, it will generate a local note URL.
* If no title is provided, it will use the URL as title.
@ -91,50 +92,42 @@ class ApiUtils
* @param array $input Request Link.
* @param bool $defaultPrivate Request Link.
*
* @return array Formatted link.
* @return Bookmark instance.
*/
public static function buildLinkFromRequest($input, $defaultPrivate)
{
$input['url'] = ! empty($input['url']) ? cleanup_url($input['url']) : '';
$bookmark = new Bookmark();
$url = ! empty($input['url']) ? cleanup_url($input['url']) : '';
if (isset($input['private'])) {
$private = filter_var($input['private'], FILTER_VALIDATE_BOOLEAN);
} else {
$private = $defaultPrivate;
}
$link = [
'title' => ! empty($input['title']) ? $input['title'] : $input['url'],
'url' => $input['url'],
'description' => ! empty($input['description']) ? $input['description'] : '',
'tags' => ! empty($input['tags']) ? implode(' ', $input['tags']) : '',
'private' => $private,
'created' => new \DateTime(),
];
return $link;
$bookmark->setTitle(! empty($input['title']) ? $input['title'] : '');
$bookmark->setUrl($url);
$bookmark->setDescription(! empty($input['description']) ? $input['description'] : '');
$bookmark->setTags(! empty($input['tags']) ? $input['tags'] : []);
$bookmark->setPrivate($private);
return $bookmark;
}
/**
* Update link fields using an updated link object.
*
* @param array $oldLink data
* @param array $newLink data
* @param Bookmark $oldLink data
* @param Bookmark $newLink data
*
* @return array $oldLink updated with $newLink values
* @return Bookmark $oldLink updated with $newLink values
*/
public static function updateLink($oldLink, $newLink)
{
foreach (['title', 'url', 'description', 'tags', 'private'] as $field) {
$oldLink[$field] = $newLink[$field];
}
$oldLink['updated'] = new \DateTime();
if (empty($oldLink['url'])) {
$oldLink['url'] = '?' . $oldLink['shorturl'];
}
if (empty($oldLink['title'])) {
$oldLink['title'] = $oldLink['url'];
}
$oldLink->setTitle($newLink->getTitle());
$oldLink->setUrl($newLink->getUrl());
$oldLink->setDescription($newLink->getDescription());
$oldLink->setTags($newLink->getTags());
$oldLink->setPrivate($newLink->isPrivate());
return $oldLink;
}
@ -143,7 +136,7 @@ class ApiUtils
* Format a Tag for the REST API.
*
* @param string $tag Tag name
* @param int $occurrences Number of links using this tag
* @param int $occurrences Number of bookmarks using this tag
*
* @return array Link data formatted for the REST API.
*/

View file

@ -2,7 +2,7 @@
namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\LinkDB;
use Shaarli\Bookmark\BookmarkServiceInterface;
use Shaarli\Config\ConfigManager;
use Slim\Container;
@ -26,9 +26,9 @@ abstract class ApiController
protected $conf;
/**
* @var LinkDB
* @var BookmarkServiceInterface
*/
protected $linkDb;
protected $bookmarkService;
/**
* @var HistoryController
@ -51,7 +51,7 @@ abstract class ApiController
{
$this->ci = $ci;
$this->conf = $ci->get('conf');
$this->linkDb = $ci->get('db');
$this->bookmarkService = $ci->get('db');
$this->history = $ci->get('history');
if ($this->conf->get('dev.debug', false)) {
$this->jsonStyle = JSON_PRETTY_PRINT;

View file

@ -41,7 +41,7 @@ class HistoryController extends ApiController
throw new ApiBadParametersException('Invalid offset');
}
// limit parameter is either a number of links or 'all' for everything.
// limit parameter is either a number of bookmarks or 'all' for everything.
$limit = $request->getParam('limit');
if (empty($limit)) {
$limit = count($history);

View file

@ -2,6 +2,7 @@
namespace Shaarli\Api\Controllers;
use Shaarli\Bookmark\BookmarkFilter;
use Slim\Http\Request;
use Slim\Http\Response;
@ -26,8 +27,8 @@ class Info extends ApiController
public function getInfo($request, $response)
{
$info = [
'global_counter' => count($this->linkDb),
'private_counter' => count_private($this->linkDb),
'global_counter' => $this->bookmarkService->count(),
'private_counter' => $this->bookmarkService->count(BookmarkFilter::$PRIVATE),
'settings' => array(
'title' => $this->conf->get('general.title', 'Shaarli'),
'header_link' => $this->conf->get('general.header_link', '?'),

View file

@ -11,7 +11,7 @@ use Slim\Http\Response;
/**
* Class Links
*
* REST API Controller: all services related to links collection.
* REST API Controller: all services related to bookmarks collection.
*
* @package Api\Controllers
* @see http://shaarli.github.io/api-documentation/#links-links-collection
@ -19,12 +19,12 @@ use Slim\Http\Response;
class Links extends ApiController
{
/**
* @var int Number of links returned if no limit is provided.
* @var int Number of bookmarks returned if no limit is provided.
*/
public static $DEFAULT_LIMIT = 20;
/**
* Retrieve a list of links, allowing different filters.
* Retrieve a list of bookmarks, allowing different filters.
*
* @param Request $request Slim request.
* @param Response $response Slim response.
@ -36,33 +36,32 @@ class Links extends ApiController
public function getLinks($request, $response)
{
$private = $request->getParam('visibility');
$links = $this->linkDb->filterSearch(
$bookmarks = $this->bookmarkService->search(
[
'searchtags' => $request->getParam('searchtags', ''),
'searchterm' => $request->getParam('searchterm', ''),
],
false,
$private
);
// Return links from the {offset}th link, starting from 0.
// Return bookmarks from the {offset}th link, starting from 0.
$offset = $request->getParam('offset');
if (! empty($offset) && ! ctype_digit($offset)) {
throw new ApiBadParametersException('Invalid offset');
}
$offset = ! empty($offset) ? intval($offset) : 0;
if ($offset > count($links)) {
if ($offset > count($bookmarks)) {
return $response->withJson([], 200, $this->jsonStyle);
}
// limit parameter is either a number of links or 'all' for everything.
// limit parameter is either a number of bookmarks or 'all' for everything.
$limit = $request->getParam('limit');
if (empty($limit)) {
$limit = self::$DEFAULT_LIMIT;
} elseif (ctype_digit($limit)) {
$limit = intval($limit);
} elseif ($limit === 'all') {
$limit = count($links);
$limit = count($bookmarks);
} else {
throw new ApiBadParametersException('Invalid limit');
}
@ -72,12 +71,12 @@ class Links extends ApiController
$out = [];
$index = 0;
foreach ($links as $link) {
foreach ($bookmarks as $bookmark) {
if (count($out) >= $limit) {
break;
}
if ($index++ >= $offset) {
$out[] = ApiUtils::formatLink($link, $indexUrl);
$out[] = ApiUtils::formatLink($bookmark, $indexUrl);
}
}
@ -97,11 +96,11 @@ class Links extends ApiController
*/
public function getLink($request, $response, $args)
{
if (!isset($this->linkDb[$args['id']])) {
if (!$this->bookmarkService->exists($args['id'])) {
throw new ApiLinkNotFoundException();
}
$index = index_url($this->ci['environment']);
$out = ApiUtils::formatLink($this->linkDb[$args['id']], $index);
$out = ApiUtils::formatLink($this->bookmarkService->get($args['id']), $index);
return $response->withJson($out, 200, $this->jsonStyle);
}
@ -117,9 +116,11 @@ class Links extends ApiController
public function postLink($request, $response)
{
$data = $request->getParsedBody();
$link = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
$bookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
// duplicate by URL, return 409 Conflict
if (! empty($link['url']) && ! empty($dup = $this->linkDb->getLinkFromUrl($link['url']))) {
if (! empty($bookmark->getUrl())
&& ! empty($dup = $this->bookmarkService->findByUrl($bookmark->getUrl()))
) {
return $response->withJson(
ApiUtils::formatLink($dup, index_url($this->ci['environment'])),
409,
@ -127,23 +128,9 @@ class Links extends ApiController
);
}
$link['id'] = $this->linkDb->getNextId();
$link['shorturl'] = link_small_hash($link['created'], $link['id']);
// note: general relative URL
if (empty($link['url'])) {
$link['url'] = '?' . $link['shorturl'];
}
if (empty($link['title'])) {
$link['title'] = $link['url'];
}
$this->linkDb[$link['id']] = $link;
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->addLink($link);
$out = ApiUtils::formatLink($link, index_url($this->ci['environment']));
$redirect = $this->ci->router->relativePathFor('getLink', ['id' => $link['id']]);
$this->bookmarkService->add($bookmark);
$out = ApiUtils::formatLink($bookmark, index_url($this->ci['environment']));
$redirect = $this->ci->router->relativePathFor('getLink', ['id' => $bookmark->getId()]);
return $response->withAddedHeader('Location', $redirect)
->withJson($out, 201, $this->jsonStyle);
}
@ -161,18 +148,18 @@ class Links extends ApiController
*/
public function putLink($request, $response, $args)
{
if (! isset($this->linkDb[$args['id']])) {
if (! $this->bookmarkService->exists($args['id'])) {
throw new ApiLinkNotFoundException();
}
$index = index_url($this->ci['environment']);
$data = $request->getParsedBody();
$requestLink = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
$requestBookmark = ApiUtils::buildLinkFromRequest($data, $this->conf->get('privacy.default_private_links'));
// duplicate URL on a different link, return 409 Conflict
if (! empty($requestLink['url'])
&& ! empty($dup = $this->linkDb->getLinkFromUrl($requestLink['url']))
&& $dup['id'] != $args['id']
if (! empty($requestBookmark->getUrl())
&& ! empty($dup = $this->bookmarkService->findByUrl($requestBookmark->getUrl()))
&& $dup->getId() != $args['id']
) {
return $response->withJson(
ApiUtils::formatLink($dup, $index),
@ -181,13 +168,11 @@ class Links extends ApiController
);
}
$responseLink = $this->linkDb[$args['id']];
$responseLink = ApiUtils::updateLink($responseLink, $requestLink);
$this->linkDb[$responseLink['id']] = $responseLink;
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->updateLink($responseLink);
$responseBookmark = $this->bookmarkService->get($args['id']);
$responseBookmark = ApiUtils::updateLink($responseBookmark, $requestBookmark);
$this->bookmarkService->set($responseBookmark);
$out = ApiUtils::formatLink($responseLink, $index);
$out = ApiUtils::formatLink($responseBookmark, $index);
return $response->withJson($out, 200, $this->jsonStyle);
}
@ -204,13 +189,11 @@ class Links extends ApiController
*/
public function deleteLink($request, $response, $args)
{
if (! isset($this->linkDb[$args['id']])) {
if (! $this->bookmarkService->exists($args['id'])) {
throw new ApiLinkNotFoundException();
}
$link = $this->linkDb[$args['id']];
unset($this->linkDb[(int) $args['id']]);
$this->linkDb->save($this->conf->get('resource.page_cache'));
$this->history->deleteLink($link);
$bookmark = $this->bookmarkService->get($args['id']);
$this->bookmarkService->remove($bookmark);
return $response->withStatus(204);
}

View file

@ -5,6 +5,7 @@ namespace Shaarli\Api\Controllers;
use Shaarli\Api\ApiUtils;
use Shaarli\Api\Exceptions\ApiBadParametersException;
use Shaarli\Api\Exceptions\ApiTagNotFoundException;
use Shaarli\Bookmark\BookmarkFilter;
use Slim\Http\Request;
use Slim\Http\Response;
@ -18,7 +19,7 @@ use Slim\Http\Response;
class Tags extends ApiController
{
/**
* @var int Number of links returned if no limit is provided.
* @var int Number of bookmarks returned if no limit is provided.
*/
public static $DEFAULT_LIMIT = 'all';
@ -35,7 +36,7 @@ class Tags extends ApiController
public function getTags($request, $response)
{
$visibility = $request->getParam('visibility');
$tags = $this->linkDb->linksCountPerTag([], $visibility);
$tags = $this->bookmarkService->bookmarksCountPerTag([], $visibility);
// Return tags from the {offset}th tag, starting from 0.
$offset = $request->getParam('offset');
@ -47,7 +48,7 @@ class Tags extends ApiController
return $response->withJson([], 200, $this->jsonStyle);
}
// limit parameter is either a number of links or 'all' for everything.
// limit parameter is either a number of bookmarks or 'all' for everything.
$limit = $request->getParam('limit');
if (empty($limit)) {
$limit = self::$DEFAULT_LIMIT;
@ -87,7 +88,7 @@ class Tags extends ApiController
*/
public function getTag($request, $response, $args)
{
$tags = $this->linkDb->linksCountPerTag();
$tags = $this->bookmarkService->bookmarksCountPerTag();
if (!isset($tags[$args['tagName']])) {
throw new ApiTagNotFoundException();
}
@ -111,7 +112,7 @@ class Tags extends ApiController
*/
public function putTag($request, $response, $args)
{
$tags = $this->linkDb->linksCountPerTag();
$tags = $this->bookmarkService->bookmarksCountPerTag();
if (! isset($tags[$args['tagName']])) {
throw new ApiTagNotFoundException();
}
@ -121,13 +122,19 @@ class Tags extends ApiController
throw new ApiBadParametersException('New tag name is required in the request body');
}
$updated = $this->linkDb->renameTag($args['tagName'], $data['name']);
$this->linkDb->save($this->conf->get('resource.page_cache'));
foreach ($updated as $link) {
$this->history->updateLink($link);
$bookmarks = $this->bookmarkService->search(
['searchtags' => $args['tagName']],
BookmarkFilter::$ALL,
true
);
foreach ($bookmarks as $bookmark) {
$bookmark->renameTag($args['tagName'], $data['name']);
$this->bookmarkService->set($bookmark, false);
$this->history->updateLink($bookmark);
}
$this->bookmarkService->save();
$tags = $this->linkDb->linksCountPerTag();
$tags = $this->bookmarkService->bookmarksCountPerTag();
$out = ApiUtils::formatTag($data['name'], $tags[$data['name']]);
return $response->withJson($out, 200, $this->jsonStyle);
}
@ -145,15 +152,22 @@ class Tags extends ApiController
*/
public function deleteTag($request, $response, $args)
{
$tags = $this->linkDb->linksCountPerTag();
$tags = $this->bookmarkService->bookmarksCountPerTag();
if (! isset($tags[$args['tagName']])) {
throw new ApiTagNotFoundException();
}
$updated = $this->linkDb->renameTag($args['tagName'], null);
$this->linkDb->save($this->conf->get('resource.page_cache'));
foreach ($updated as $link) {
$this->history->updateLink($link);
$bookmarks = $this->bookmarkService->search(
['searchtags' => $args['tagName']],
BookmarkFilter::$ALL,
true
);
foreach ($bookmarks as $bookmark) {
$bookmark->deleteTag($args['tagName']);
$this->bookmarkService->set($bookmark, false);
$this->history->updateLink($bookmark);
}
$this->bookmarkService->save();
return $response->withStatus(204);
}