diff options
Diffstat (limited to 'src/Wallabag/ApiBundle')
6 files changed, 536 insertions, 105 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/DeveloperController.php b/src/Wallabag/ApiBundle/Controller/DeveloperController.php index 9cb1b626..9cb73f4c 100644 --- a/src/Wallabag/ApiBundle/Controller/DeveloperController.php +++ b/src/Wallabag/ApiBundle/Controller/DeveloperController.php | |||
@@ -43,7 +43,7 @@ class DeveloperController extends Controller | |||
43 | $clientForm->handleRequest($request); | 43 | $clientForm->handleRequest($request); |
44 | 44 | ||
45 | if ($clientForm->isSubmitted() && $clientForm->isValid()) { | 45 | if ($clientForm->isSubmitted() && $clientForm->isValid()) { |
46 | $client->setAllowedGrantTypes(['token', 'authorization_code', 'password', 'refresh_token']); | 46 | $client->setAllowedGrantTypes(['client_credentials', 'token', 'authorization_code', 'password', 'refresh_token']); |
47 | $em->persist($client); | 47 | $em->persist($client); |
48 | $em->flush(); | 48 | $em->flush(); |
49 | 49 | ||
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index 54c1747c..768c4fdc 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php | |||
@@ -5,6 +5,7 @@ namespace Wallabag\ApiBundle\Controller; | |||
5 | use Hateoas\Configuration\Route; | 5 | use Hateoas\Configuration\Route; |
6 | use Hateoas\Representation\Factory\PagerfantaFactory; | 6 | use Hateoas\Representation\Factory\PagerfantaFactory; |
7 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | 7 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
8 | use Symfony\Component\HttpKernel\Exception\HttpException; | ||
8 | use Symfony\Component\HttpFoundation\Request; | 9 | use Symfony\Component\HttpFoundation\Request; |
9 | use Symfony\Component\HttpFoundation\JsonResponse; | 10 | use Symfony\Component\HttpFoundation\JsonResponse; |
10 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | 11 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; |
@@ -41,12 +42,10 @@ class EntryRestController extends WallabagRestController | |||
41 | ->getRepository('WallabagCoreBundle:Entry') | 42 | ->getRepository('WallabagCoreBundle:Entry') |
42 | ->findByUrlAndUserId($url, $this->getUser()->getId()); | 43 | ->findByUrlAndUserId($url, $this->getUser()->getId()); |
43 | 44 | ||
44 | $results[$url] = false === $res ? false : true; | 45 | $results[$url] = $res instanceof Entry ? $res->getId() : false; |
45 | } | 46 | } |
46 | 47 | ||
47 | $json = $this->get('serializer')->serialize($results, 'json'); | 48 | return $this->sendResponse($results); |
48 | |||
49 | return (new JsonResponse())->setJson($json); | ||
50 | } | 49 | } |
51 | 50 | ||
52 | // let's see if it is a simple url? | 51 | // let's see if it is a simple url? |
@@ -60,11 +59,9 @@ class EntryRestController extends WallabagRestController | |||
60 | ->getRepository('WallabagCoreBundle:Entry') | 59 | ->getRepository('WallabagCoreBundle:Entry') |
61 | ->findByUrlAndUserId($url, $this->getUser()->getId()); | 60 | ->findByUrlAndUserId($url, $this->getUser()->getId()); |
62 | 61 | ||
63 | $exists = false === $res ? false : true; | 62 | $exists = $res instanceof Entry ? $res->getId() : false; |
64 | |||
65 | $json = $this->get('serializer')->serialize(['exists' => $exists], 'json'); | ||
66 | 63 | ||
67 | return (new JsonResponse())->setJson($json); | 64 | return $this->sendResponse(['exists' => $exists]); |
68 | } | 65 | } |
69 | 66 | ||
70 | /** | 67 | /** |
@@ -80,6 +77,7 @@ class EntryRestController extends WallabagRestController | |||
80 | * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}, | 77 | * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}, |
81 | * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."}, | 78 | * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."}, |
82 | * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."}, | 79 | * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."}, |
80 | * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"}, | ||
83 | * } | 81 | * } |
84 | * ) | 82 | * ) |
85 | * | 83 | * |
@@ -91,6 +89,7 @@ class EntryRestController extends WallabagRestController | |||
91 | 89 | ||
92 | $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive'); | 90 | $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive'); |
93 | $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred'); | 91 | $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred'); |
92 | $isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public'); | ||
94 | $sort = $request->query->get('sort', 'created'); | 93 | $sort = $request->query->get('sort', 'created'); |
95 | $order = $request->query->get('order', 'desc'); | 94 | $order = $request->query->get('order', 'desc'); |
96 | $page = (int) $request->query->get('page', 1); | 95 | $page = (int) $request->query->get('page', 1); |
@@ -99,9 +98,16 @@ class EntryRestController extends WallabagRestController | |||
99 | $since = $request->query->get('since', 0); | 98 | $since = $request->query->get('since', 0); |
100 | 99 | ||
101 | /** @var \Pagerfanta\Pagerfanta $pager */ | 100 | /** @var \Pagerfanta\Pagerfanta $pager */ |
102 | $pager = $this->getDoctrine() | 101 | $pager = $this->get('wallabag_core.entry_repository')->findEntries( |
103 | ->getRepository('WallabagCoreBundle:Entry') | 102 | $this->getUser()->getId(), |
104 | ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags); | 103 | $isArchived, |
104 | $isStarred, | ||
105 | $isPublic, | ||
106 | $sort, | ||
107 | $order, | ||
108 | $since, | ||
109 | $tags | ||
110 | ); | ||
105 | 111 | ||
106 | $pager->setMaxPerPage($perPage); | 112 | $pager->setMaxPerPage($perPage); |
107 | $pager->setCurrentPage($page); | 113 | $pager->setCurrentPage($page); |
@@ -114,6 +120,7 @@ class EntryRestController extends WallabagRestController | |||
114 | [ | 120 | [ |
115 | 'archive' => $isArchived, | 121 | 'archive' => $isArchived, |
116 | 'starred' => $isStarred, | 122 | 'starred' => $isStarred, |
123 | 'public' => $isPublic, | ||
117 | 'sort' => $sort, | 124 | 'sort' => $sort, |
118 | 'order' => $order, | 125 | 'order' => $order, |
119 | 'page' => $page, | 126 | 'page' => $page, |
@@ -125,9 +132,7 @@ class EntryRestController extends WallabagRestController | |||
125 | ) | 132 | ) |
126 | ); | 133 | ); |
127 | 134 | ||
128 | $json = $this->get('serializer')->serialize($paginatedCollection, 'json'); | 135 | return $this->sendResponse($paginatedCollection); |
129 | |||
130 | return (new JsonResponse())->setJson($json); | ||
131 | } | 136 | } |
132 | 137 | ||
133 | /** | 138 | /** |
@@ -146,9 +151,7 @@ class EntryRestController extends WallabagRestController | |||
146 | $this->validateAuthentication(); | 151 | $this->validateAuthentication(); |
147 | $this->validateUserAccess($entry->getUser()->getId()); | 152 | $this->validateUserAccess($entry->getUser()->getId()); |
148 | 153 | ||
149 | $json = $this->get('serializer')->serialize($entry, 'json'); | 154 | return $this->sendResponse($entry); |
150 | |||
151 | return (new JsonResponse())->setJson($json); | ||
152 | } | 155 | } |
153 | 156 | ||
154 | /** | 157 | /** |
@@ -174,74 +177,153 @@ class EntryRestController extends WallabagRestController | |||
174 | } | 177 | } |
175 | 178 | ||
176 | /** | 179 | /** |
177 | * Create an entry. | 180 | * Handles an entries list and delete URL. |
178 | * | 181 | * |
179 | * @ApiDoc( | 182 | * @ApiDoc( |
180 | * parameters={ | 183 | * parameters={ |
181 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, | 184 | * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."} |
182 | * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."}, | ||
183 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, | ||
184 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, | ||
185 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, | ||
186 | * } | 185 | * } |
187 | * ) | 186 | * ) |
188 | * | 187 | * |
189 | * @return JsonResponse | 188 | * @return JsonResponse |
190 | */ | 189 | */ |
191 | public function postEntriesAction(Request $request) | 190 | public function deleteEntriesListAction(Request $request) |
192 | { | 191 | { |
193 | $this->validateAuthentication(); | 192 | $this->validateAuthentication(); |
194 | 193 | ||
195 | $url = $request->request->get('url'); | 194 | $urls = json_decode($request->query->get('urls', [])); |
196 | $title = $request->request->get('title'); | 195 | |
197 | $isArchived = $request->request->get('archive'); | 196 | if (empty($urls)) { |
198 | $isStarred = $request->request->get('starred'); | 197 | return $this->sendResponse([]); |
198 | } | ||
199 | 199 | ||
200 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId()); | 200 | $results = []; |
201 | 201 | ||
202 | if (false === $entry) { | 202 | // handle multiple urls |
203 | $entry = new Entry($this->getUser()); | 203 | foreach ($urls as $key => $url) { |
204 | try { | 204 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( |
205 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry( | 205 | $url, |
206 | $entry, | 206 | $this->getUser()->getId() |
207 | $url | 207 | ); |
208 | ); | 208 | |
209 | } catch (\Exception $e) { | 209 | $results[$key]['url'] = $url; |
210 | $this->get('logger')->error('Error while saving an entry', [ | 210 | |
211 | 'exception' => $e, | 211 | if (false !== $entry) { |
212 | 'entry' => $entry, | 212 | $em = $this->getDoctrine()->getManager(); |
213 | ]); | 213 | $em->remove($entry); |
214 | $entry->setUrl($url); | 214 | $em->flush(); |
215 | |||
216 | // entry deleted, dispatch event about it! | ||
217 | $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); | ||
215 | } | 218 | } |
216 | } | ||
217 | 219 | ||
218 | if (!is_null($title)) { | 220 | $results[$key]['entry'] = $entry instanceof Entry ? true : false; |
219 | $entry->setTitle($title); | ||
220 | } | 221 | } |
221 | 222 | ||
222 | $tags = $request->request->get('tags', ''); | 223 | return $this->sendResponse($results); |
223 | if (!empty($tags)) { | 224 | } |
224 | $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); | 225 | |
226 | /** | ||
227 | * Handles an entries list and create URL. | ||
228 | * | ||
229 | * @ApiDoc( | ||
230 | * parameters={ | ||
231 | * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."} | ||
232 | * } | ||
233 | * ) | ||
234 | * | ||
235 | * @return JsonResponse | ||
236 | * | ||
237 | * @throws HttpException When limit is reached | ||
238 | */ | ||
239 | public function postEntriesListAction(Request $request) | ||
240 | { | ||
241 | $this->validateAuthentication(); | ||
242 | |||
243 | $urls = json_decode($request->query->get('urls', [])); | ||
244 | |||
245 | $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions'); | ||
246 | |||
247 | if (count($urls) > $limit) { | ||
248 | throw new HttpException(400, 'API limit reached'); | ||
225 | } | 249 | } |
226 | 250 | ||
227 | if (!is_null($isStarred)) { | 251 | $results = []; |
228 | $entry->setStarred((bool) $isStarred); | 252 | if (empty($urls)) { |
253 | return $this->sendResponse($results); | ||
229 | } | 254 | } |
230 | 255 | ||
231 | if (!is_null($isArchived)) { | 256 | // handle multiple urls |
232 | $entry->setArchived((bool) $isArchived); | 257 | foreach ($urls as $key => $url) { |
258 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( | ||
259 | $url, | ||
260 | $this->getUser()->getId() | ||
261 | ); | ||
262 | |||
263 | $results[$key]['url'] = $url; | ||
264 | |||
265 | if (false === $entry) { | ||
266 | $entry = new Entry($this->getUser()); | ||
267 | |||
268 | $this->get('wallabag_core.content_proxy')->updateEntry($entry, $url); | ||
269 | } | ||
270 | |||
271 | $em = $this->getDoctrine()->getManager(); | ||
272 | $em->persist($entry); | ||
273 | $em->flush(); | ||
274 | |||
275 | $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; | ||
276 | |||
277 | // entry saved, dispatch event about it! | ||
278 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | ||
233 | } | 279 | } |
234 | 280 | ||
235 | $em = $this->getDoctrine()->getManager(); | 281 | return $this->sendResponse($results); |
236 | $em->persist($entry); | 282 | } |
237 | $em->flush(); | ||
238 | 283 | ||
239 | // entry saved, dispatch event about it! | 284 | /** |
240 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | 285 | * Create an entry. |
286 | * | ||
287 | * If you want to provide the HTML content (which means wallabag won't fetch it from the url), you must provide `content`, `title` & `url` fields **non-empty**. | ||
288 | * Otherwise, content will be fetched as normal from the url and values will be overwritten. | ||
289 | * | ||
290 | * @ApiDoc( | ||
291 | * parameters={ | ||
292 | * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, | ||
293 | * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."}, | ||
294 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, | ||
295 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"}, | ||
296 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"}, | ||
297 | * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"}, | ||
298 | * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"}, | ||
299 | * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"}, | ||
300 | * {"name"="published_at", "dataType"="datetime|integer", "format"="YYYY-MM-DDTHH:II:SS+TZ or a timestamp", "required"=false, "description"="Published date of the entry"}, | ||
301 | * {"name"="authors", "dataType"="string", "format"="Name Firstname,author2,author3", "required"=false, "description"="Authors of the entry"}, | ||
302 | * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="will generate a public link for the entry"}, | ||
303 | * } | ||
304 | * ) | ||
305 | * | ||
306 | * @return JsonResponse | ||
307 | */ | ||
308 | public function postEntriesAction(Request $request) | ||
309 | { | ||
310 | $this->validateAuthentication(); | ||
241 | 311 | ||
242 | $json = $this->get('serializer')->serialize($entry, 'json'); | 312 | $url = $request->request->get('url'); |
243 | 313 | ||
244 | return (new JsonResponse())->setJson($json); | 314 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( |
315 | $url, | ||
316 | $this->getUser()->getId() | ||
317 | ); | ||
318 | |||
319 | if (false === $entry) { | ||
320 | $entry = new Entry($this->getUser()); | ||
321 | $entry->setUrl($url); | ||
322 | } | ||
323 | |||
324 | $this->upsertEntry($entry, $request); | ||
325 | |||
326 | return $this->sendResponse($entry); | ||
245 | } | 327 | } |
246 | 328 | ||
247 | /** | 329 | /** |
@@ -256,6 +338,12 @@ class EntryRestController extends WallabagRestController | |||
256 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, | 338 | * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, |
257 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."}, | 339 | * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."}, |
258 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."}, | 340 | * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."}, |
341 | * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"}, | ||
342 | * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"}, | ||
343 | * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"}, | ||
344 | * {"name"="published_at", "dataType"="datetime|integer", "format"="YYYY-MM-DDTHH:II:SS+TZ or a timestamp", "required"=false, "description"="Published date of the entry"}, | ||
345 | * {"name"="authors", "dataType"="string", "format"="Name Firstname,author2,author3", "required"=false, "description"="Authors of the entry"}, | ||
346 | * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="will generate a public link for the entry"}, | ||
259 | * } | 347 | * } |
260 | * ) | 348 | * ) |
261 | * | 349 | * |
@@ -266,33 +354,9 @@ class EntryRestController extends WallabagRestController | |||
266 | $this->validateAuthentication(); | 354 | $this->validateAuthentication(); |
267 | $this->validateUserAccess($entry->getUser()->getId()); | 355 | $this->validateUserAccess($entry->getUser()->getId()); |
268 | 356 | ||
269 | $title = $request->request->get('title'); | 357 | $this->upsertEntry($entry, $request, true); |
270 | $isArchived = $request->request->get('archive'); | ||
271 | $isStarred = $request->request->get('starred'); | ||
272 | |||
273 | if (!is_null($title)) { | ||
274 | $entry->setTitle($title); | ||
275 | } | ||
276 | |||
277 | if (!is_null($isArchived)) { | ||
278 | $entry->setArchived((bool) $isArchived); | ||
279 | } | ||
280 | |||
281 | if (!is_null($isStarred)) { | ||
282 | $entry->setStarred((bool) $isStarred); | ||
283 | } | ||
284 | |||
285 | $tags = $request->request->get('tags', ''); | ||
286 | if (!empty($tags)) { | ||
287 | $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); | ||
288 | } | ||
289 | |||
290 | $em = $this->getDoctrine()->getManager(); | ||
291 | $em->flush(); | ||
292 | |||
293 | $json = $this->get('serializer')->serialize($entry, 'json'); | ||
294 | 358 | ||
295 | return (new JsonResponse())->setJson($json); | 359 | return $this->sendResponse($entry); |
296 | } | 360 | } |
297 | 361 | ||
298 | /** | 362 | /** |
@@ -313,7 +377,7 @@ class EntryRestController extends WallabagRestController | |||
313 | $this->validateUserAccess($entry->getUser()->getId()); | 377 | $this->validateUserAccess($entry->getUser()->getId()); |
314 | 378 | ||
315 | try { | 379 | try { |
316 | $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); | 380 | $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); |
317 | } catch (\Exception $e) { | 381 | } catch (\Exception $e) { |
318 | $this->get('logger')->error('Error while saving an entry', [ | 382 | $this->get('logger')->error('Error while saving an entry', [ |
319 | 'exception' => $e, | 383 | 'exception' => $e, |
@@ -335,9 +399,7 @@ class EntryRestController extends WallabagRestController | |||
335 | // entry saved, dispatch event about it! | 399 | // entry saved, dispatch event about it! |
336 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | 400 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); |
337 | 401 | ||
338 | $json = $this->get('serializer')->serialize($entry, 'json'); | 402 | return $this->sendResponse($entry); |
339 | |||
340 | return (new JsonResponse())->setJson($json); | ||
341 | } | 403 | } |
342 | 404 | ||
343 | /** | 405 | /** |
@@ -363,9 +425,7 @@ class EntryRestController extends WallabagRestController | |||
363 | // entry deleted, dispatch event about it! | 425 | // entry deleted, dispatch event about it! |
364 | $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); | 426 | $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); |
365 | 427 | ||
366 | $json = $this->get('serializer')->serialize($entry, 'json'); | 428 | return $this->sendResponse($entry); |
367 | |||
368 | return (new JsonResponse())->setJson($json); | ||
369 | } | 429 | } |
370 | 430 | ||
371 | /** | 431 | /** |
@@ -384,9 +444,7 @@ class EntryRestController extends WallabagRestController | |||
384 | $this->validateAuthentication(); | 444 | $this->validateAuthentication(); |
385 | $this->validateUserAccess($entry->getUser()->getId()); | 445 | $this->validateUserAccess($entry->getUser()->getId()); |
386 | 446 | ||
387 | $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); | 447 | return $this->sendResponse($entry->getTags()); |
388 | |||
389 | return (new JsonResponse())->setJson($json); | ||
390 | } | 448 | } |
391 | 449 | ||
392 | /** | 450 | /** |
@@ -410,16 +468,14 @@ class EntryRestController extends WallabagRestController | |||
410 | 468 | ||
411 | $tags = $request->request->get('tags', ''); | 469 | $tags = $request->request->get('tags', ''); |
412 | if (!empty($tags)) { | 470 | if (!empty($tags)) { |
413 | $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); | 471 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); |
414 | } | 472 | } |
415 | 473 | ||
416 | $em = $this->getDoctrine()->getManager(); | 474 | $em = $this->getDoctrine()->getManager(); |
417 | $em->persist($entry); | 475 | $em->persist($entry); |
418 | $em->flush(); | 476 | $em->flush(); |
419 | 477 | ||
420 | $json = $this->get('serializer')->serialize($entry, 'json'); | 478 | return $this->sendResponse($entry); |
421 | |||
422 | return (new JsonResponse())->setJson($json); | ||
423 | } | 479 | } |
424 | 480 | ||
425 | /** | 481 | /** |
@@ -444,8 +500,198 @@ class EntryRestController extends WallabagRestController | |||
444 | $em->persist($entry); | 500 | $em->persist($entry); |
445 | $em->flush(); | 501 | $em->flush(); |
446 | 502 | ||
447 | $json = $this->get('serializer')->serialize($entry, 'json'); | 503 | return $this->sendResponse($entry); |
504 | } | ||
505 | |||
506 | /** | ||
507 | * Handles an entries list delete tags from them. | ||
508 | * | ||
509 | * @ApiDoc( | ||
510 | * parameters={ | ||
511 | * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."} | ||
512 | * } | ||
513 | * ) | ||
514 | * | ||
515 | * @return JsonResponse | ||
516 | */ | ||
517 | public function deleteEntriesTagsListAction(Request $request) | ||
518 | { | ||
519 | $this->validateAuthentication(); | ||
520 | |||
521 | $list = json_decode($request->query->get('list', [])); | ||
522 | |||
523 | if (empty($list)) { | ||
524 | return $this->sendResponse([]); | ||
525 | } | ||
526 | |||
527 | // handle multiple urls | ||
528 | $results = []; | ||
529 | |||
530 | foreach ($list as $key => $element) { | ||
531 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( | ||
532 | $element->url, | ||
533 | $this->getUser()->getId() | ||
534 | ); | ||
535 | |||
536 | $results[$key]['url'] = $element->url; | ||
537 | $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; | ||
538 | |||
539 | $tags = $element->tags; | ||
540 | |||
541 | if (false !== $entry && !(empty($tags))) { | ||
542 | $tags = explode(',', $tags); | ||
543 | foreach ($tags as $label) { | ||
544 | $label = trim($label); | ||
545 | |||
546 | $tag = $this->getDoctrine() | ||
547 | ->getRepository('WallabagCoreBundle:Tag') | ||
548 | ->findOneByLabel($label); | ||
549 | |||
550 | if (false !== $tag) { | ||
551 | $entry->removeTag($tag); | ||
552 | } | ||
553 | } | ||
554 | |||
555 | $em = $this->getDoctrine()->getManager(); | ||
556 | $em->persist($entry); | ||
557 | $em->flush(); | ||
558 | } | ||
559 | } | ||
560 | |||
561 | return $this->sendResponse($results); | ||
562 | } | ||
563 | |||
564 | /** | ||
565 | * Handles an entries list and add tags to them. | ||
566 | * | ||
567 | * @ApiDoc( | ||
568 | * parameters={ | ||
569 | * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."} | ||
570 | * } | ||
571 | * ) | ||
572 | * | ||
573 | * @return JsonResponse | ||
574 | */ | ||
575 | public function postEntriesTagsListAction(Request $request) | ||
576 | { | ||
577 | $this->validateAuthentication(); | ||
578 | |||
579 | $list = json_decode($request->query->get('list', [])); | ||
580 | |||
581 | if (empty($list)) { | ||
582 | return $this->sendResponse([]); | ||
583 | } | ||
584 | |||
585 | $results = []; | ||
586 | |||
587 | // handle multiple urls | ||
588 | foreach ($list as $key => $element) { | ||
589 | $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( | ||
590 | $element->url, | ||
591 | $this->getUser()->getId() | ||
592 | ); | ||
593 | |||
594 | $results[$key]['url'] = $element->url; | ||
595 | $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; | ||
596 | |||
597 | $tags = $element->tags; | ||
598 | |||
599 | if (false !== $entry && !(empty($tags))) { | ||
600 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); | ||
601 | |||
602 | $em = $this->getDoctrine()->getManager(); | ||
603 | $em->persist($entry); | ||
604 | $em->flush(); | ||
605 | } | ||
606 | } | ||
607 | |||
608 | return $this->sendResponse($results); | ||
609 | } | ||
610 | |||
611 | /** | ||
612 | * Shortcut to send data serialized in json. | ||
613 | * | ||
614 | * @param mixed $data | ||
615 | * | ||
616 | * @return JsonResponse | ||
617 | */ | ||
618 | private function sendResponse($data) | ||
619 | { | ||
620 | $json = $this->get('serializer')->serialize($data, 'json'); | ||
448 | 621 | ||
449 | return (new JsonResponse())->setJson($json); | 622 | return (new JsonResponse())->setJson($json); |
450 | } | 623 | } |
624 | |||
625 | /** | ||
626 | * Update or Insert a new entry. | ||
627 | * | ||
628 | * @param Entry $entry | ||
629 | * @param Request $request | ||
630 | * @param bool $disableContentUpdate If we don't want the content to be update by fetching the url (used when patching instead of posting) | ||
631 | */ | ||
632 | private function upsertEntry(Entry $entry, Request $request, $disableContentUpdate = false) | ||
633 | { | ||
634 | $title = $request->request->get('title'); | ||
635 | $tags = $request->request->get('tags', []); | ||
636 | $isArchived = $request->request->get('archive'); | ||
637 | $isStarred = $request->request->get('starred'); | ||
638 | $isPublic = $request->request->get('public'); | ||
639 | $content = $request->request->get('content'); | ||
640 | $language = $request->request->get('language'); | ||
641 | $picture = $request->request->get('preview_picture'); | ||
642 | $publishedAt = $request->request->get('published_at'); | ||
643 | $authors = $request->request->get('authors', ''); | ||
644 | |||
645 | try { | ||
646 | $this->get('wallabag_core.content_proxy')->updateEntry( | ||
647 | $entry, | ||
648 | $entry->getUrl(), | ||
649 | [ | ||
650 | 'title' => !empty($title) ? $title : $entry->getTitle(), | ||
651 | 'html' => !empty($content) ? $content : $entry->getContent(), | ||
652 | 'url' => $entry->getUrl(), | ||
653 | 'language' => !empty($language) ? $language : $entry->getLanguage(), | ||
654 | 'date' => !empty($publishedAt) ? $publishedAt : $entry->getPublishedAt(), | ||
655 | // faking the open graph preview picture | ||
656 | 'open_graph' => [ | ||
657 | 'og_image' => !empty($picture) ? $picture : $entry->getPreviewPicture(), | ||
658 | ], | ||
659 | 'authors' => is_string($authors) ? explode(',', $authors) : $entry->getPublishedBy(), | ||
660 | ], | ||
661 | $disableContentUpdate | ||
662 | ); | ||
663 | } catch (\Exception $e) { | ||
664 | $this->get('logger')->error('Error while saving an entry', [ | ||
665 | 'exception' => $e, | ||
666 | 'entry' => $entry, | ||
667 | ]); | ||
668 | } | ||
669 | |||
670 | if (!is_null($isArchived)) { | ||
671 | $entry->setArchived((bool) $isArchived); | ||
672 | } | ||
673 | |||
674 | if (!is_null($isStarred)) { | ||
675 | $entry->setStarred((bool) $isStarred); | ||
676 | } | ||
677 | |||
678 | if (!empty($tags)) { | ||
679 | $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags); | ||
680 | } | ||
681 | |||
682 | if (!is_null($isPublic)) { | ||
683 | if (true === (bool) $isPublic && null === $entry->getUid()) { | ||
684 | $entry->generateUid(); | ||
685 | } elseif (false === (bool) $isPublic) { | ||
686 | $entry->cleanUid(); | ||
687 | } | ||
688 | } | ||
689 | |||
690 | $em = $this->getDoctrine()->getManager(); | ||
691 | $em->persist($entry); | ||
692 | $em->flush(); | ||
693 | |||
694 | // entry saved, dispatch event about it! | ||
695 | $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); | ||
696 | } | ||
451 | } | 697 | } |
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php index bc6d4e64..354187a0 100644 --- a/src/Wallabag/ApiBundle/Controller/TagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php | |||
@@ -31,7 +31,7 @@ class TagRestController extends WallabagRestController | |||
31 | } | 31 | } |
32 | 32 | ||
33 | /** | 33 | /** |
34 | * Permanently remove one tag from **every** entry. | 34 | * Permanently remove one tag from **every** entry by passing the Tag label. |
35 | * | 35 | * |
36 | * @ApiDoc( | 36 | * @ApiDoc( |
37 | * requirements={ | 37 | * requirements={ |
@@ -44,7 +44,7 @@ class TagRestController extends WallabagRestController | |||
44 | public function deleteTagLabelAction(Request $request) | 44 | public function deleteTagLabelAction(Request $request) |
45 | { | 45 | { |
46 | $this->validateAuthentication(); | 46 | $this->validateAuthentication(); |
47 | $label = $request->request->get('tag', ''); | 47 | $label = $request->get('tag', ''); |
48 | 48 | ||
49 | $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); | 49 | $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); |
50 | 50 | ||
@@ -78,7 +78,7 @@ class TagRestController extends WallabagRestController | |||
78 | { | 78 | { |
79 | $this->validateAuthentication(); | 79 | $this->validateAuthentication(); |
80 | 80 | ||
81 | $tagsLabels = $request->request->get('tags', ''); | 81 | $tagsLabels = $request->get('tags', ''); |
82 | 82 | ||
83 | $tags = []; | 83 | $tags = []; |
84 | 84 | ||
@@ -106,7 +106,7 @@ class TagRestController extends WallabagRestController | |||
106 | } | 106 | } |
107 | 107 | ||
108 | /** | 108 | /** |
109 | * Permanently remove one tag from **every** entry. | 109 | * Permanently remove one tag from **every** entry by passing the Tag ID. |
110 | * | 110 | * |
111 | * @ApiDoc( | 111 | * @ApiDoc( |
112 | * requirements={ | 112 | * requirements={ |
diff --git a/src/Wallabag/ApiBundle/Controller/UserRestController.php b/src/Wallabag/ApiBundle/Controller/UserRestController.php new file mode 100644 index 00000000..7471f5f6 --- /dev/null +++ b/src/Wallabag/ApiBundle/Controller/UserRestController.php | |||
@@ -0,0 +1,157 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\ApiBundle\Controller; | ||
4 | |||
5 | use FOS\UserBundle\Event\UserEvent; | ||
6 | use FOS\UserBundle\FOSUserEvents; | ||
7 | use JMS\Serializer\SerializationContext; | ||
8 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | ||
9 | use Symfony\Component\HttpFoundation\Request; | ||
10 | use Symfony\Component\HttpFoundation\JsonResponse; | ||
11 | use Wallabag\UserBundle\Entity\User; | ||
12 | use Wallabag\ApiBundle\Entity\Client; | ||
13 | |||
14 | class UserRestController extends WallabagRestController | ||
15 | { | ||
16 | /** | ||
17 | * Retrieve current logged in user informations. | ||
18 | * | ||
19 | * @ApiDoc() | ||
20 | * | ||
21 | * @return JsonResponse | ||
22 | */ | ||
23 | public function getUserAction() | ||
24 | { | ||
25 | $this->validateAuthentication(); | ||
26 | |||
27 | return $this->sendUser($this->getUser()); | ||
28 | } | ||
29 | |||
30 | /** | ||
31 | * Register an user and create a client. | ||
32 | * | ||
33 | * @ApiDoc( | ||
34 | * requirements={ | ||
35 | * {"name"="username", "dataType"="string", "required"=true, "description"="The user's username"}, | ||
36 | * {"name"="password", "dataType"="string", "required"=true, "description"="The user's password"}, | ||
37 | * {"name"="email", "dataType"="string", "required"=true, "description"="The user's email"}, | ||
38 | * {"name"="client_name", "dataType"="string", "required"=true, "description"="The client name (to be used by your app)"} | ||
39 | * } | ||
40 | * ) | ||
41 | * | ||
42 | * @todo Make this method (or the whole API) accessible only through https | ||
43 | * | ||
44 | * @return JsonResponse | ||
45 | */ | ||
46 | public function putUserAction(Request $request) | ||
47 | { | ||
48 | if (!$this->getParameter('fosuser_registration') || !$this->get('craue_config')->get('api_user_registration')) { | ||
49 | $json = $this->get('serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json'); | ||
50 | |||
51 | return (new JsonResponse()) | ||
52 | ->setJson($json) | ||
53 | ->setStatusCode(JsonResponse::HTTP_FORBIDDEN); | ||
54 | } | ||
55 | |||
56 | $userManager = $this->get('fos_user.user_manager'); | ||
57 | $user = $userManager->createUser(); | ||
58 | // user will be disabled BY DEFAULT to avoid spamming account to be enabled | ||
59 | $user->setEnabled(false); | ||
60 | |||
61 | $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [ | ||
62 | 'csrf_protection' => false, | ||
63 | ]); | ||
64 | |||
65 | // simulate form submission | ||
66 | $form->submit([ | ||
67 | 'username' => $request->request->get('username'), | ||
68 | 'plainPassword' => [ | ||
69 | 'first' => $request->request->get('password'), | ||
70 | 'second' => $request->request->get('password'), | ||
71 | ], | ||
72 | 'email' => $request->request->get('email'), | ||
73 | ]); | ||
74 | |||
75 | if ($form->isSubmitted() && false === $form->isValid()) { | ||
76 | $view = $this->view($form, 400); | ||
77 | $view->setFormat('json'); | ||
78 | |||
79 | // handle errors in a more beautiful way than the default view | ||
80 | $data = json_decode($this->handleView($view)->getContent(), true)['children']; | ||
81 | $errors = []; | ||
82 | |||
83 | if (isset($data['username']['errors'])) { | ||
84 | $errors['username'] = $this->translateErrors($data['username']['errors']); | ||
85 | } | ||
86 | |||
87 | if (isset($data['email']['errors'])) { | ||
88 | $errors['email'] = $this->translateErrors($data['email']['errors']); | ||
89 | } | ||
90 | |||
91 | if (isset($data['plainPassword']['children']['first']['errors'])) { | ||
92 | $errors['password'] = $this->translateErrors($data['plainPassword']['children']['first']['errors']); | ||
93 | } | ||
94 | |||
95 | $json = $this->get('serializer')->serialize(['error' => $errors], 'json'); | ||
96 | |||
97 | return (new JsonResponse()) | ||
98 | ->setJson($json) | ||
99 | ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST); | ||
100 | } | ||
101 | |||
102 | // create a default client | ||
103 | $client = new Client($user); | ||
104 | $client->setName($request->request->get('client_name', 'Default client')); | ||
105 | |||
106 | $this->getDoctrine()->getManager()->persist($client); | ||
107 | |||
108 | $user->addClient($client); | ||
109 | |||
110 | $userManager->updateUser($user); | ||
111 | |||
112 | // dispatch a created event so the associated config will be created | ||
113 | $event = new UserEvent($user, $request); | ||
114 | $this->get('event_dispatcher')->dispatch(FOSUserEvents::USER_CREATED, $event); | ||
115 | |||
116 | return $this->sendUser($user, 'user_api_with_client', JsonResponse::HTTP_CREATED); | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * Send user response. | ||
121 | * | ||
122 | * @param User $user | ||
123 | * @param string $group Used to define with serialized group might be used | ||
124 | * @param int $status HTTP Status code to send | ||
125 | * | ||
126 | * @return JsonResponse | ||
127 | */ | ||
128 | private function sendUser(User $user, $group = 'user_api', $status = JsonResponse::HTTP_OK) | ||
129 | { | ||
130 | $json = $this->get('serializer')->serialize( | ||
131 | $user, | ||
132 | 'json', | ||
133 | SerializationContext::create()->setGroups([$group]) | ||
134 | ); | ||
135 | |||
136 | return (new JsonResponse()) | ||
137 | ->setJson($json) | ||
138 | ->setStatusCode($status); | ||
139 | } | ||
140 | |||
141 | /** | ||
142 | * Translate errors message. | ||
143 | * | ||
144 | * @param array $errors | ||
145 | * | ||
146 | * @return array | ||
147 | */ | ||
148 | private function translateErrors($errors) | ||
149 | { | ||
150 | $translatedErrors = []; | ||
151 | foreach ($errors as $error) { | ||
152 | $translatedErrors[] = $this->get('translator')->trans($error); | ||
153 | } | ||
154 | |||
155 | return $translatedErrors; | ||
156 | } | ||
157 | } | ||
diff --git a/src/Wallabag/ApiBundle/Entity/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php index 9ed9f980..c15fd3fa 100644 --- a/src/Wallabag/ApiBundle/Entity/Client.php +++ b/src/Wallabag/ApiBundle/Entity/Client.php | |||
@@ -5,6 +5,9 @@ namespace Wallabag\ApiBundle\Entity; | |||
5 | use Doctrine\ORM\Mapping as ORM; | 5 | use Doctrine\ORM\Mapping as ORM; |
6 | use FOS\OAuthServerBundle\Entity\Client as BaseClient; | 6 | use FOS\OAuthServerBundle\Entity\Client as BaseClient; |
7 | use Wallabag\UserBundle\Entity\User; | 7 | use Wallabag\UserBundle\Entity\User; |
8 | use JMS\Serializer\Annotation\Groups; | ||
9 | use JMS\Serializer\Annotation\SerializedName; | ||
10 | use JMS\Serializer\Annotation\VirtualProperty; | ||
8 | 11 | ||
9 | /** | 12 | /** |
10 | * @ORM\Table("oauth2_clients") | 13 | * @ORM\Table("oauth2_clients") |
@@ -23,6 +26,8 @@ class Client extends BaseClient | |||
23 | * @var string | 26 | * @var string |
24 | * | 27 | * |
25 | * @ORM\Column(name="name", type="text", nullable=false) | 28 | * @ORM\Column(name="name", type="text", nullable=false) |
29 | * | ||
30 | * @Groups({"user_api_with_client"}) | ||
26 | */ | 31 | */ |
27 | protected $name; | 32 | protected $name; |
28 | 33 | ||
@@ -37,6 +42,14 @@ class Client extends BaseClient | |||
37 | protected $accessTokens; | 42 | protected $accessTokens; |
38 | 43 | ||
39 | /** | 44 | /** |
45 | * @var string | ||
46 | * | ||
47 | * @SerializedName("client_secret") | ||
48 | * @Groups({"user_api_with_client"}) | ||
49 | */ | ||
50 | protected $secret; | ||
51 | |||
52 | /** | ||
40 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="clients") | 53 | * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="clients") |
41 | */ | 54 | */ |
42 | private $user; | 55 | private $user; |
@@ -78,4 +91,14 @@ class Client extends BaseClient | |||
78 | { | 91 | { |
79 | return $this->user; | 92 | return $this->user; |
80 | } | 93 | } |
94 | |||
95 | /** | ||
96 | * @VirtualProperty | ||
97 | * @SerializedName("client_id") | ||
98 | * @Groups({"user_api_with_client"}) | ||
99 | */ | ||
100 | public function getClientId() | ||
101 | { | ||
102 | return $this->getId().'_'.$this->getRandomId(); | ||
103 | } | ||
81 | } | 104 | } |
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml index 57d37f4b..c0283e71 100644 --- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml +++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml | |||
@@ -17,3 +17,8 @@ misc: | |||
17 | type: rest | 17 | type: rest |
18 | resource: "WallabagApiBundle:WallabagRest" | 18 | resource: "WallabagApiBundle:WallabagRest" |
19 | name_prefix: api_ | 19 | name_prefix: api_ |
20 | |||
21 | user: | ||
22 | type: rest | ||
23 | resource: "WallabagApiBundle:UserRest" | ||
24 | name_prefix: api_ | ||