]> git.immae.eu Git - github/wallabag/wallabag.git/blame - src/Wallabag/ApiBundle/Controller/EntryRestController.php
Added API endpoint to handle a list of URL
[github/wallabag/wallabag.git] / src / Wallabag / ApiBundle / Controller / EntryRestController.php
CommitLineData
900c8448
NL
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
11use Wallabag\CoreBundle\Entity\Entry;
12use Wallabag\CoreBundle\Entity\Tag;
5a619812
JB
13use Wallabag\CoreBundle\Event\EntrySavedEvent;
14use Wallabag\CoreBundle\Event\EntryDeletedEvent;
900c8448
NL
15
16class EntryRestController extends WallabagRestController
17{
18 /**
19 * Check if an entry exist by url.
20 *
21 * @ApiDoc(
22 * parameters={
23 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
24 * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="Urls (as an array) to check if it exists"}
25 * }
26 * )
27 *
28 * @return JsonResponse
29 */
30 public function getEntriesExistsAction(Request $request)
31 {
32 $this->validateAuthentication();
33
34 $urls = $request->query->get('urls', []);
35
36 // handle multiple urls first
37 if (!empty($urls)) {
38 $results = [];
39 foreach ($urls as $url) {
40 $res = $this->getDoctrine()
41 ->getRepository('WallabagCoreBundle:Entry')
42 ->findByUrlAndUserId($url, $this->getUser()->getId());
43
ca9a83ee 44 $results[$url] = $res instanceof Entry ? $res->getId() : false;
900c8448
NL
45 }
46
47 $json = $this->get('serializer')->serialize($results, 'json');
48
49 return (new JsonResponse())->setJson($json);
50 }
51
52 // let's see if it is a simple url?
53 $url = $request->query->get('url', '');
54
55 if (empty($url)) {
56 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
57 }
58
59 $res = $this->getDoctrine()
60 ->getRepository('WallabagCoreBundle:Entry')
61 ->findByUrlAndUserId($url, $this->getUser()->getId());
62
ca9a83ee 63 $exists = $res instanceof Entry ? $res->getId() : false;
900c8448
NL
64
65 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
66
67 return (new JsonResponse())->setJson($json);
68 }
69
70 /**
71 * Retrieve all entries. It could be filtered by many options.
72 *
73 * @ApiDoc(
74 * parameters={
75 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
76 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
77 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
78 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
79 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
80 * {"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."},
82 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
83 * }
84 * )
85 *
86 * @return JsonResponse
87 */
88 public function getEntriesAction(Request $request)
89 {
90 $this->validateAuthentication();
91
92 $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');
94 $sort = $request->query->get('sort', 'created');
95 $order = $request->query->get('order', 'desc');
96 $page = (int) $request->query->get('page', 1);
97 $perPage = (int) $request->query->get('perPage', 30);
98 $tags = $request->query->get('tags', '');
99 $since = $request->query->get('since', 0);
100
101 $pager = $this->getDoctrine()
102 ->getRepository('WallabagCoreBundle:Entry')
103 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
104
105 $pager->setCurrentPage($page);
106 $pager->setMaxPerPage($perPage);
107
108 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
109 $paginatedCollection = $pagerfantaFactory->createRepresentation(
110 $pager,
111 new Route(
112 'api_get_entries',
113 [
114 'archive' => $isArchived,
115 'starred' => $isStarred,
116 'sort' => $sort,
117 'order' => $order,
118 'page' => $page,
119 'perPage' => $perPage,
120 'tags' => $tags,
121 'since' => $since,
122 ],
123 UrlGeneratorInterface::ABSOLUTE_URL
124 )
125 );
126
127 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
128
129 return (new JsonResponse())->setJson($json);
130 }
131
132 /**
133 * Retrieve a single entry.
134 *
135 * @ApiDoc(
136 * requirements={
137 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
138 * }
139 * )
140 *
141 * @return JsonResponse
142 */
143 public function getEntryAction(Entry $entry)
144 {
145 $this->validateAuthentication();
146 $this->validateUserAccess($entry->getUser()->getId());
147
148 $json = $this->get('serializer')->serialize($entry, 'json');
149
150 return (new JsonResponse())->setJson($json);
151 }
152
864c1dd2
JB
153 /**
154 * Retrieve a single entry as a predefined format.
155 *
156 * @ApiDoc(
157 * requirements={
158 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
159 * }
160 * )
161 *
162 * @return Response
163 */
164 public function getEntryExportAction(Entry $entry, Request $request)
165 {
166 $this->validateAuthentication();
167 $this->validateUserAccess($entry->getUser()->getId());
168
169 return $this->get('wallabag_core.helper.entries_export')
170 ->setEntries($entry)
171 ->updateTitle('entry')
172 ->exportAs($request->attributes->get('_format'));
173 }
174
1eca7831
NL
175 /**
176 * Handles an entries list and create or remove URL.
177 *
178 * @ApiDoc(
179 * parameters={
180 * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...', 'action': 'delete'}, {'url': 'http://...', 'action': 'add'}]", "description"="Urls (as an array) to handle."}
181 * }
182 * )
183 *
184 * @return JsonResponse
185 */
186 public function postEntriesListAction(Request $request)
187 {
188 $this->validateAuthentication();
189
190 $list = json_decode($request->query->get('list', []));
191 $results = [];
192
193 // handle multiple urls
194 if (!empty($list)) {
195 $results = [];
196 foreach ($list as $key => $element) {
197 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
198 $element->url,
199 $this->getUser()->getId()
200 );
201
202 $results[$key]['url'] = $element->url;
203 $results[$key]['action'] = $element->action;
204
205 switch ($element->action) {
206 case 'delete':
207 if (false !== $entry) {
208 $em = $this->getDoctrine()->getManager();
209 $em->remove($entry);
210 $em->flush();
211
212 // entry deleted, dispatch event about it!
213 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
214 }
215
216 $results[$key]['entry'] = $entry instanceof Entry ? true : false;
217
218 break;
219 case 'add':
220 if (false === $entry) {
221 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
222 new Entry($this->getUser()),
223 $element->url
224 );
225 }
226
227 $em = $this->getDoctrine()->getManager();
228 $em->persist($entry);
229 $em->flush();
230
231 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
232
233 // entry saved, dispatch event about it!
234 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
235
236 break;
237 }
238 }
239 }
240
241 $json = $this->get('serializer')->serialize($results, 'json');
242
243 return (new JsonResponse())->setJson($json);
244 }
245
900c8448
NL
246 /**
247 * Create an entry.
248 *
249 * @ApiDoc(
250 * parameters={
251 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
252 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
253 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
254 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
255 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
256 * }
257 * )
258 *
259 * @return JsonResponse
260 */
261 public function postEntriesAction(Request $request)
262 {
263 $this->validateAuthentication();
264
265 $url = $request->request->get('url');
266 $title = $request->request->get('title');
267 $isArchived = $request->request->get('archive');
268 $isStarred = $request->request->get('starred');
269
270 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
271
272 if (false === $entry) {
273 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
274 new Entry($this->getUser()),
275 $url
276 );
277 }
278
279 if (!is_null($title)) {
280 $entry->setTitle($title);
281 }
282
283 $tags = $request->request->get('tags', '');
284 if (!empty($tags)) {
285 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
286 }
287
288 if (!is_null($isStarred)) {
289 $entry->setStarred((bool) $isStarred);
290 }
291
292 if (!is_null($isArchived)) {
293 $entry->setArchived((bool) $isArchived);
294 }
295
296 $em = $this->getDoctrine()->getManager();
297 $em->persist($entry);
900c8448
NL
298 $em->flush();
299
5a619812
JB
300 // entry saved, dispatch event about it!
301 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
302
900c8448
NL
303 $json = $this->get('serializer')->serialize($entry, 'json');
304
305 return (new JsonResponse())->setJson($json);
306 }
307
308 /**
309 * Change several properties of an entry.
310 *
311 * @ApiDoc(
312 * requirements={
313 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
314 * },
315 * parameters={
316 * {"name"="title", "dataType"="string", "required"=false},
317 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
318 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
319 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
320 * }
321 * )
322 *
323 * @return JsonResponse
324 */
325 public function patchEntriesAction(Entry $entry, Request $request)
326 {
327 $this->validateAuthentication();
328 $this->validateUserAccess($entry->getUser()->getId());
329
330 $title = $request->request->get('title');
331 $isArchived = $request->request->get('archive');
332 $isStarred = $request->request->get('starred');
333
334 if (!is_null($title)) {
335 $entry->setTitle($title);
336 }
337
338 if (!is_null($isArchived)) {
339 $entry->setArchived((bool) $isArchived);
340 }
341
342 if (!is_null($isStarred)) {
343 $entry->setStarred((bool) $isStarred);
344 }
345
346 $tags = $request->request->get('tags', '');
347 if (!empty($tags)) {
348 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
349 }
350
351 $em = $this->getDoctrine()->getManager();
352 $em->flush();
353
354 $json = $this->get('serializer')->serialize($entry, 'json');
355
356 return (new JsonResponse())->setJson($json);
357 }
358
0a6f4568
JB
359 /**
360 * Reload an entry.
5cd0857e 361 * An empty response with HTTP Status 304 will be send if we weren't able to update the content (because it hasn't changed or we got an error).
0a6f4568
JB
362 *
363 * @ApiDoc(
364 * requirements={
365 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
366 * }
367 * )
368 *
369 * @return JsonResponse
370 */
371 public function patchEntriesReloadAction(Entry $entry)
372 {
373 $this->validateAuthentication();
374 $this->validateUserAccess($entry->getUser()->getId());
375
0a6f4568
JB
376 try {
377 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
378 } catch (\Exception $e) {
379 $this->get('logger')->error('Error while saving an entry', [
380 'exception' => $e,
381 'entry' => $entry,
382 ]);
383
5cd0857e 384 return new JsonResponse([], 304);
0a6f4568
JB
385 }
386
387 // if refreshing entry failed, don't save it
388 if ($this->getParameter('wallabag_core.fetching_error_message') === $entry->getContent()) {
5cd0857e 389 return new JsonResponse([], 304);
0a6f4568
JB
390 }
391
392 $em = $this->getDoctrine()->getManager();
393 $em->persist($entry);
394 $em->flush();
395
396 // entry saved, dispatch event about it!
397 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
398
399 $json = $this->get('serializer')->serialize($entry, 'json');
400
401 return (new JsonResponse())->setJson($json);
402 }
403
900c8448
NL
404 /**
405 * Delete **permanently** an entry.
406 *
407 * @ApiDoc(
408 * requirements={
409 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
410 * }
411 * )
412 *
413 * @return JsonResponse
414 */
415 public function deleteEntriesAction(Entry $entry)
416 {
417 $this->validateAuthentication();
418 $this->validateUserAccess($entry->getUser()->getId());
419
420 $em = $this->getDoctrine()->getManager();
421 $em->remove($entry);
422 $em->flush();
423
5a619812
JB
424 // entry deleted, dispatch event about it!
425 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
426
900c8448
NL
427 $json = $this->get('serializer')->serialize($entry, 'json');
428
429 return (new JsonResponse())->setJson($json);
430 }
431
432 /**
433 * Retrieve all tags for an entry.
434 *
435 * @ApiDoc(
436 * requirements={
437 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
438 * }
439 * )
440 *
441 * @return JsonResponse
442 */
443 public function getEntriesTagsAction(Entry $entry)
444 {
445 $this->validateAuthentication();
446 $this->validateUserAccess($entry->getUser()->getId());
447
448 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
449
450 return (new JsonResponse())->setJson($json);
451 }
452
453 /**
454 * Add one or more tags to an entry.
455 *
456 * @ApiDoc(
457 * requirements={
458 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
459 * },
460 * parameters={
461 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
462 * }
463 * )
464 *
465 * @return JsonResponse
466 */
467 public function postEntriesTagsAction(Request $request, Entry $entry)
468 {
469 $this->validateAuthentication();
470 $this->validateUserAccess($entry->getUser()->getId());
471
472 $tags = $request->request->get('tags', '');
473 if (!empty($tags)) {
474 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
475 }
476
477 $em = $this->getDoctrine()->getManager();
478 $em->persist($entry);
479 $em->flush();
480
481 $json = $this->get('serializer')->serialize($entry, 'json');
482
483 return (new JsonResponse())->setJson($json);
484 }
485
486 /**
487 * Permanently remove one tag for an entry.
488 *
489 * @ApiDoc(
490 * requirements={
491 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
492 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
493 * }
494 * )
495 *
496 * @return JsonResponse
497 */
498 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
499 {
500 $this->validateAuthentication();
501 $this->validateUserAccess($entry->getUser()->getId());
502
503 $entry->removeTag($tag);
504 $em = $this->getDoctrine()->getManager();
505 $em->persist($entry);
506 $em->flush();
507
508 $json = $this->get('serializer')->serialize($entry, 'json');
509
510 return (new JsonResponse())->setJson($json);
511 }
d1fc5902
NL
512
513 /**
80299ed2 514 * Handles an entries list delete tags from them.
d1fc5902
NL
515 *
516 * @ApiDoc(
517 * parameters={
80299ed2 518 * {"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."}
d1fc5902
NL
519 * }
520 * )
521 *
522 * @return JsonResponse
523 */
80299ed2 524 public function deleteEntriesTagsListAction(Request $request)
d1fc5902
NL
525 {
526 $this->validateAuthentication();
527
528 $list = json_decode($request->query->get('list', []));
529 $results = [];
530
531 // handle multiple urls
532 if (!empty($list)) {
d1fc5902
NL
533 foreach ($list as $key => $element) {
534 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
535 $element->url,
536 $this->getUser()->getId()
537 );
538
539 $results[$key]['url'] = $element->url;
d1fc5902
NL
540 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
541
542 $tags = $element->tags;
543
544 if (false !== $entry && !(empty($tags))) {
80299ed2
NL
545 $tags = explode(',', $tags);
546 foreach ($tags as $label) {
547 $label = trim($label);
548
549 $tag = $this->getDoctrine()
550 ->getRepository('WallabagCoreBundle:Tag')
551 ->findOneByLabel($label);
552
553 if (false !== $tag) {
554 $entry->removeTag($tag);
555 }
d1fc5902
NL
556 }
557
558 $em = $this->getDoctrine()->getManager();
559 $em->persist($entry);
560 $em->flush();
561 }
562 }
563 }
564
565 $json = $this->get('serializer')->serialize($results, 'json');
566
567 return (new JsonResponse())->setJson($json);
568 }
80299ed2
NL
569
570 /**
571 * Handles an entries list and add tags to them.
572 *
573 * @ApiDoc(
574 * parameters={
575 * {"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."}
576 * }
577 * )
578 *
579 * @return JsonResponse
580 */
581 public function postEntriesTagsListAction(Request $request)
582 {
583 $this->validateAuthentication();
584
585 $list = json_decode($request->query->get('list', []));
586 $results = [];
587
588 // handle multiple urls
589 if (!empty($list)) {
80299ed2
NL
590 foreach ($list as $key => $element) {
591 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
592 $element->url,
593 $this->getUser()->getId()
594 );
595
596 $results[$key]['url'] = $element->url;
597 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
598
599 $tags = $element->tags;
600
601 if (false !== $entry && !(empty($tags))) {
602 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
603
604 $em = $this->getDoctrine()->getManager();
605 $em->persist($entry);
606 $em->flush();
607 }
608 }
609 }
610
611 $json = $this->get('serializer')->serialize($results, 'json');
612
613 return (new JsonResponse())->setJson($json);
614 }
900c8448 615}