diff options
author | ArthurHoaro <arthur@hoa.ro> | 2018-05-19 15:04:04 +0200 |
---|---|---|
committer | ArthurHoaro <arthur@hoa.ro> | 2018-06-04 18:51:22 +0200 |
commit | d3f42ca487287447efb81061609644108044a038 (patch) | |
tree | 6e9d5a33290f857c8f7a04dbdf8cf0ca749db149 /application | |
parent | 17e45b2e9c33c736751e059276fadb480f98e621 (diff) | |
download | Shaarli-d3f42ca487287447efb81061609644108044a038.tar.gz Shaarli-d3f42ca487287447efb81061609644108044a038.tar.zst Shaarli-d3f42ca487287447efb81061609644108044a038.zip |
Implements Tags endpoints for Shaarli's REST API
Endpoints:
* List All Tags [GET]
* Get a tag [GET]
* Update a tag [PUT]
* Delete a tag [DELETE]
Fixes #904
References shaarli/api-documentation#34
Diffstat (limited to 'application')
-rw-r--r-- | application/api/ApiUtils.php | 16 | ||||
-rw-r--r-- | application/api/controllers/Links.php | 8 | ||||
-rw-r--r-- | application/api/controllers/Tags.php | 161 | ||||
-rw-r--r-- | application/api/exceptions/ApiTagNotFoundException.php | 32 |
4 files changed, 213 insertions, 4 deletions
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/Links.php b/application/api/controllers/Links.php index 3a9c0355..ffcfd4c7 100644 --- a/application/api/controllers/Links.php +++ b/application/api/controllers/Links.php | |||
@@ -68,16 +68,16 @@ class Links extends ApiController | |||
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 | |||
3 | namespace Shaarli\Api\Controllers; | ||
4 | |||
5 | use Shaarli\Api\ApiUtils; | ||
6 | use Shaarli\Api\Exceptions\ApiBadParametersException; | ||
7 | use Shaarli\Api\Exceptions\ApiLinkNotFoundException; | ||
8 | use Shaarli\Api\Exceptions\ApiTagNotFoundException; | ||
9 | use Slim\Http\Request; | ||
10 | use 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 | */ | ||
19 | class 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/ApiTagNotFoundException.php b/application/api/exceptions/ApiTagNotFoundException.php new file mode 100644 index 00000000..eed5afa5 --- /dev/null +++ b/application/api/exceptions/ApiTagNotFoundException.php | |||
@@ -0,0 +1,32 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Shaarli\Api\Exceptions; | ||
4 | |||
5 | |||
6 | use Slim\Http\Response; | ||
7 | |||
8 | /** | ||
9 | * Class ApiTagNotFoundException | ||
10 | * | ||
11 | * Tag selected by name couldn't be found in the datastore, results in a 404 error. | ||
12 | * | ||
13 | * @package Shaarli\Api\Exceptions | ||
14 | */ | ||
15 | class ApiTagNotFoundException extends ApiException | ||
16 | { | ||
17 | /** | ||
18 | * ApiLinkNotFoundException constructor. | ||
19 | */ | ||
20 | public function __construct() | ||
21 | { | ||
22 | $this->message = 'Tag not found'; | ||
23 | } | ||
24 | |||
25 | /** | ||
26 | * {@inheritdoc} | ||
27 | */ | ||
28 | public function getApiResponse() | ||
29 | { | ||
30 | return $this->buildApiResponse(404); | ||
31 | } | ||
32 | } | ||