aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/api
diff options
context:
space:
mode:
Diffstat (limited to 'application/api')
-rw-r--r--application/api/ApiMiddleware.php5
-rw-r--r--application/api/ApiUtils.php16
-rw-r--r--application/api/controllers/ApiController.php2
-rw-r--r--application/api/controllers/History.php5
-rw-r--r--application/api/controllers/Info.php4
-rw-r--r--application/api/controllers/Links.php12
-rw-r--r--application/api/controllers/Tags.php161
-rw-r--r--application/api/exceptions/ApiException.php8
-rw-r--r--application/api/exceptions/ApiLinkNotFoundException.php1
-rw-r--r--application/api/exceptions/ApiTagNotFoundException.php31
10 files changed, 227 insertions, 18 deletions
diff --git a/application/api/ApiMiddleware.php b/application/api/ApiMiddleware.php
index ff209393..66eac133 100644
--- a/application/api/ApiMiddleware.php
+++ b/application/api/ApiMiddleware.php
@@ -65,7 +65,7 @@ class ApiMiddleware
65 try { 65 try {
66 $this->checkRequest($request); 66 $this->checkRequest($request);
67 $response = $next($request, $response); 67 $response = $next($request, $response);
68 } catch(ApiException $e) { 68 } catch (ApiException $e) {
69 $e->setResponse($response); 69 $e->setResponse($response);
70 $e->setDebug($this->conf->get('dev.debug', false)); 70 $e->setDebug($this->conf->get('dev.debug', false));
71 $response = $e->getApiResponse(); 71 $response = $e->getApiResponse();
@@ -98,7 +98,8 @@ class ApiMiddleware
98 * 98 *
99 * @throws ApiAuthorizationException The token couldn't be validated. 99 * @throws ApiAuthorizationException The token couldn't be validated.
100 */ 100 */
101 protected function checkToken($request) { 101 protected function checkToken($request)
102 {
102 if (! $request->hasHeader('Authorization')) { 103 if (! $request->hasHeader('Authorization')) {
103 throw new ApiAuthorizationException('JWT token not provided'); 104 throw new ApiAuthorizationException('JWT token not provided');
104 } 105 }
diff --git a/application/api/ApiUtils.php b/application/api/ApiUtils.php
index f154bb52..fc5ecaf1 100644
--- a/application/api/ApiUtils.php
+++ b/application/api/ApiUtils.php
@@ -134,4 +134,20 @@ class ApiUtils
134 134
135 return $oldLink; 135 return $oldLink;
136 } 136 }
137
138 /**
139 * Format a Tag for the REST API.
140 *
141 * @param string $tag Tag name
142 * @param int $occurrences Number of links using this tag
143 *
144 * @return array Link data formatted for the REST API.
145 */
146 public static function formatTag($tag, $occurences)
147 {
148 return [
149 'name' => $tag,
150 'occurrences' => $occurences,
151 ];
152 }
137} 153}
diff --git a/application/api/controllers/ApiController.php b/application/api/controllers/ApiController.php
index 3be85b98..9edefcf6 100644
--- a/application/api/controllers/ApiController.php
+++ b/application/api/controllers/ApiController.php
@@ -41,7 +41,7 @@ abstract class ApiController
41 41
42 /** 42 /**
43 * ApiController constructor. 43 * ApiController constructor.
44 * 44 *
45 * Note: enabling debug mode displays JSON with readable formatting. 45 * Note: enabling debug mode displays JSON with readable formatting.
46 * 46 *
47 * @param Container $ci Slim container. 47 * @param Container $ci Slim container.
diff --git a/application/api/controllers/History.php b/application/api/controllers/History.php
index 2ff9deaf..4582e8b2 100644
--- a/application/api/controllers/History.php
+++ b/application/api/controllers/History.php
@@ -35,8 +35,7 @@ class History extends ApiController
35 $offset = $request->getParam('offset'); 35 $offset = $request->getParam('offset');
36 if (empty($offset)) { 36 if (empty($offset)) {
37 $offset = 0; 37 $offset = 0;
38 } 38 } elseif (ctype_digit($offset)) {
39 else if (ctype_digit($offset)) {
40 $offset = (int) $offset; 39 $offset = (int) $offset;
41 } else { 40 } else {
42 throw new ApiBadParametersException('Invalid offset'); 41 throw new ApiBadParametersException('Invalid offset');
@@ -46,7 +45,7 @@ class History extends ApiController
46 $limit = $request->getParam('limit'); 45 $limit = $request->getParam('limit');
47 if (empty($limit)) { 46 if (empty($limit)) {
48 $limit = count($history); 47 $limit = count($history);
49 } else if (ctype_digit($limit)) { 48 } elseif (ctype_digit($limit)) {
50 $limit = (int) $limit; 49 $limit = (int) $limit;
51 } else { 50 } else {
52 throw new ApiBadParametersException('Invalid limit'); 51 throw new ApiBadParametersException('Invalid limit');
diff --git a/application/api/controllers/Info.php b/application/api/controllers/Info.php
index 25433f72..f37dcae5 100644
--- a/application/api/controllers/Info.php
+++ b/application/api/controllers/Info.php
@@ -7,7 +7,7 @@ use Slim\Http\Response;
7 7
8/** 8/**
9 * Class Info 9 * Class Info
10 * 10 *
11 * REST API Controller: /info 11 * REST API Controller: /info
12 * 12 *
13 * @package Api\Controllers 13 * @package Api\Controllers
@@ -17,7 +17,7 @@ class Info extends ApiController
17{ 17{
18 /** 18 /**
19 * Service providing various information about Shaarli instance. 19 * Service providing various information about Shaarli instance.
20 * 20 *
21 * @param Request $request Slim request. 21 * @param Request $request Slim request.
22 * @param Response $response Slim response. 22 * @param Response $response Slim response.
23 * 23 *
diff --git a/application/api/controllers/Links.php b/application/api/controllers/Links.php
index eb78dd26..ffcfd4c7 100644
--- a/application/api/controllers/Links.php
+++ b/application/api/controllers/Links.php
@@ -59,25 +59,25 @@ class Links extends ApiController
59 $limit = $request->getParam('limit'); 59 $limit = $request->getParam('limit');
60 if (empty($limit)) { 60 if (empty($limit)) {
61 $limit = self::$DEFAULT_LIMIT; 61 $limit = self::$DEFAULT_LIMIT;
62 } else if (ctype_digit($limit)) { 62 } elseif (ctype_digit($limit)) {
63 $limit = intval($limit); 63 $limit = intval($limit);
64 } else if ($limit === 'all') { 64 } elseif ($limit === 'all') {
65 $limit = count($links); 65 $limit = count($links);
66 } else { 66 } else {
67 throw new ApiBadParametersException('Invalid limit'); 67 throw new ApiBadParametersException('Invalid limit');
68 } 68 }
69 69
70 // 'environment' is set by Slim and encapsulate $_SERVER. 70 // 'environment' is set by Slim and encapsulate $_SERVER.
71 $index = index_url($this->ci['environment']); 71 $indexUrl = index_url($this->ci['environment']);
72 72
73 $out = []; 73 $out = [];
74 $cpt = 0; 74 $index = 0;
75 foreach ($links as $link) { 75 foreach ($links as $link) {
76 if (count($out) >= $limit) { 76 if (count($out) >= $limit) {
77 break; 77 break;
78 } 78 }
79 if ($cpt++ >= $offset) { 79 if ($index++ >= $offset) {
80 $out[] = ApiUtils::formatLink($link, $index); 80 $out[] = ApiUtils::formatLink($link, $indexUrl);
81 } 81 }
82 } 82 }
83 83
diff --git a/application/api/controllers/Tags.php b/application/api/controllers/Tags.php
new file mode 100644
index 00000000..6dd78750
--- /dev/null
+++ b/application/api/controllers/Tags.php
@@ -0,0 +1,161 @@
1<?php
2
3namespace Shaarli\Api\Controllers;
4
5use Shaarli\Api\ApiUtils;
6use Shaarli\Api\Exceptions\ApiBadParametersException;
7use Shaarli\Api\Exceptions\ApiLinkNotFoundException;
8use Shaarli\Api\Exceptions\ApiTagNotFoundException;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Class Tags
14 *
15 * REST API Controller: all services related to tags collection.
16 *
17 * @package Api\Controllers
18 */
19class Tags extends ApiController
20{
21 /**
22 * @var int Number of links returned if no limit is provided.
23 */
24 public static $DEFAULT_LIMIT = 'all';
25
26 /**
27 * Retrieve a list of tags, allowing different filters.
28 *
29 * @param Request $request Slim request.
30 * @param Response $response Slim response.
31 *
32 * @return Response response.
33 *
34 * @throws ApiBadParametersException Invalid parameters.
35 */
36 public function getTags($request, $response)
37 {
38 $visibility = $request->getParam('visibility');
39 $tags = $this->linkDb->linksCountPerTag([], $visibility);
40
41 // Return tags from the {offset}th tag, starting from 0.
42 $offset = $request->getParam('offset');
43 if (! empty($offset) && ! ctype_digit($offset)) {
44 throw new ApiBadParametersException('Invalid offset');
45 }
46 $offset = ! empty($offset) ? intval($offset) : 0;
47 if ($offset > count($tags)) {
48 return $response->withJson([], 200, $this->jsonStyle);
49 }
50
51 // limit parameter is either a number of links or 'all' for everything.
52 $limit = $request->getParam('limit');
53 if (empty($limit)) {
54 $limit = self::$DEFAULT_LIMIT;
55 }
56 if (ctype_digit($limit)) {
57 $limit = intval($limit);
58 } elseif ($limit === 'all') {
59 $limit = count($tags);
60 } else {
61 throw new ApiBadParametersException('Invalid limit');
62 }
63
64 $out = [];
65 $index = 0;
66 foreach ($tags as $tag => $occurrences) {
67 if (count($out) >= $limit) {
68 break;
69 }
70 if ($index++ >= $offset) {
71 $out[] = ApiUtils::formatTag($tag, $occurrences);
72 }
73 }
74
75 return $response->withJson($out, 200, $this->jsonStyle);
76 }
77
78 /**
79 * Return a single formatted tag by its name.
80 *
81 * @param Request $request Slim request.
82 * @param Response $response Slim response.
83 * @param array $args Path parameters. including the tag name.
84 *
85 * @return Response containing the link array.
86 *
87 * @throws ApiTagNotFoundException generating a 404 error.
88 */
89 public function getTag($request, $response, $args)
90 {
91 $tags = $this->linkDb->linksCountPerTag();
92 if (!isset($tags[$args['tagName']])) {
93 throw new ApiTagNotFoundException();
94 }
95 $out = ApiUtils::formatTag($args['tagName'], $tags[$args['tagName']]);
96
97 return $response->withJson($out, 200, $this->jsonStyle);
98 }
99
100 /**
101 * Rename a tag from the given name.
102 * If the new name provided matches an existing tag, they will be merged.
103 *
104 * @param Request $request Slim request.
105 * @param Response $response Slim response.
106 * @param array $args Path parameters. including the tag name.
107 *
108 * @return Response response.
109 *
110 * @throws ApiTagNotFoundException generating a 404 error.
111 * @throws ApiBadParametersException new tag name not provided
112 */
113 public function putTag($request, $response, $args)
114 {
115 $tags = $this->linkDb->linksCountPerTag();
116 if (! isset($tags[$args['tagName']])) {
117 throw new ApiTagNotFoundException();
118 }
119
120 $data = $request->getParsedBody();
121 if (empty($data['name'])) {
122 throw new ApiBadParametersException('New tag name is required in the request body');
123 }
124
125 $updated = $this->linkDb->renameTag($args['tagName'], $data['name']);
126 $this->linkDb->save($this->conf->get('resource.page_cache'));
127 foreach ($updated as $link) {
128 $this->history->updateLink($link);
129 }
130
131 $tags = $this->linkDb->linksCountPerTag();
132 $out = ApiUtils::formatTag($data['name'], $tags[$data['name']]);
133 return $response->withJson($out, 200, $this->jsonStyle);
134 }
135
136 /**
137 * Delete an existing tag by its name.
138 *
139 * @param Request $request Slim request.
140 * @param Response $response Slim response.
141 * @param array $args Path parameters. including the tag name.
142 *
143 * @return Response response.
144 *
145 * @throws ApiTagNotFoundException generating a 404 error.
146 */
147 public function deleteTag($request, $response, $args)
148 {
149 $tags = $this->linkDb->linksCountPerTag();
150 if (! isset($tags[$args['tagName']])) {
151 throw new ApiTagNotFoundException();
152 }
153 $updated = $this->linkDb->renameTag($args['tagName'], null);
154 $this->linkDb->save($this->conf->get('resource.page_cache'));
155 foreach ($updated as $link) {
156 $this->history->updateLink($link);
157 }
158
159 return $response->withStatus(204);
160 }
161}
diff --git a/application/api/exceptions/ApiException.php b/application/api/exceptions/ApiException.php
index c8490e0c..d6b66323 100644
--- a/application/api/exceptions/ApiException.php
+++ b/application/api/exceptions/ApiException.php
@@ -10,7 +10,8 @@ use Slim\Http\Response;
10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface). 10 * Parent Exception related to the API, able to generate a valid Response (ResponseInterface).
11 * Also can include various information in debug mode. 11 * Also can include various information in debug mode.
12 */ 12 */
13abstract class ApiException extends \Exception { 13abstract class ApiException extends \Exception
14{
14 15
15 /** 16 /**
16 * @var Response instance from Slim. 17 * @var Response instance from Slim.
@@ -27,7 +28,7 @@ abstract class ApiException extends \Exception {
27 * 28 *
28 * @return Response Final response to give. 29 * @return Response Final response to give.
29 */ 30 */
30 public abstract function getApiResponse(); 31 abstract public function getApiResponse();
31 32
32 /** 33 /**
33 * Creates ApiResponse body. 34 * Creates ApiResponse body.
@@ -36,7 +37,8 @@ abstract class ApiException extends \Exception {
36 * 37 *
37 * @return array|string response body 38 * @return array|string response body
38 */ 39 */
39 protected function getApiResponseBody() { 40 protected function getApiResponseBody()
41 {
40 if ($this->debug !== true) { 42 if ($this->debug !== true) {
41 return $this->getMessage(); 43 return $this->getMessage();
42 } 44 }
diff --git a/application/api/exceptions/ApiLinkNotFoundException.php b/application/api/exceptions/ApiLinkNotFoundException.php
index de7e14f5..c727f4f0 100644
--- a/application/api/exceptions/ApiLinkNotFoundException.php
+++ b/application/api/exceptions/ApiLinkNotFoundException.php
@@ -2,7 +2,6 @@
2 2
3namespace Shaarli\Api\Exceptions; 3namespace Shaarli\Api\Exceptions;
4 4
5
6use Slim\Http\Response; 5use Slim\Http\Response;
7 6
8/** 7/**
diff --git a/application/api/exceptions/ApiTagNotFoundException.php b/application/api/exceptions/ApiTagNotFoundException.php
new file mode 100644
index 00000000..eee152fe
--- /dev/null
+++ b/application/api/exceptions/ApiTagNotFoundException.php
@@ -0,0 +1,31 @@
1<?php
2
3namespace Shaarli\Api\Exceptions;
4
5use Slim\Http\Response;
6
7/**
8 * Class ApiTagNotFoundException
9 *
10 * Tag selected by name couldn't be found in the datastore, results in a 404 error.
11 *
12 * @package Shaarli\Api\Exceptions
13 */
14class ApiTagNotFoundException extends ApiException
15{
16 /**
17 * ApiLinkNotFoundException constructor.
18 */
19 public function __construct()
20 {
21 $this->message = 'Tag not found';
22 }
23
24 /**
25 * {@inheritdoc}
26 */
27 public function getApiResponse()
28 {
29 return $this->buildApiResponse(404);
30 }
31}