diff options
author | Jérémy Benoist <j0k3r@users.noreply.github.com> | 2019-01-23 09:19:37 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-23 09:19:37 +0100 |
commit | c6024246b744e411175318065f7c396bbb5a213e (patch) | |
tree | 635cbb8cd75888990e90c52317db72068196291d | |
parent | a8f4f7665c98753cca15140c283d49e0aa4cd0ab (diff) | |
parent | 801042544444d58580d87d04d5602797027153fc (diff) | |
download | wallabag-c6024246b744e411175318065f7c396bbb5a213e.tar.gz wallabag-c6024246b744e411175318065f7c396bbb5a213e.tar.zst wallabag-c6024246b744e411175318065f7c396bbb5a213e.zip |
Merge pull request #3627 from craig0990/feature/add-search-api-endpoint2.4
Add a basic Search REST endpoint
5 files changed, 159 insertions, 21 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php index f792aaf2..16d8a40b 100644 --- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php +++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php | |||
@@ -4,14 +4,12 @@ namespace Wallabag\ApiBundle\Controller; | |||
4 | 4 | ||
5 | use Hateoas\Configuration\Route; | 5 | use Hateoas\Configuration\Route; |
6 | use Hateoas\Representation\Factory\PagerfantaFactory; | 6 | use Hateoas\Representation\Factory\PagerfantaFactory; |
7 | use JMS\Serializer\SerializationContext; | ||
8 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | 7 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
9 | use Symfony\Component\HttpFoundation\JsonResponse; | 8 | use Symfony\Component\HttpFoundation\JsonResponse; |
10 | use Symfony\Component\HttpFoundation\Request; | 9 | use Symfony\Component\HttpFoundation\Request; |
11 | use Symfony\Component\HttpFoundation\Response; | 10 | use Symfony\Component\HttpFoundation\Response; |
12 | use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; | 11 | use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; |
13 | use Symfony\Component\HttpKernel\Exception\HttpException; | 12 | use Symfony\Component\HttpKernel\Exception\HttpException; |
14 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
15 | use Wallabag\CoreBundle\Entity\Entry; | 13 | use Wallabag\CoreBundle\Entity\Entry; |
16 | use Wallabag\CoreBundle\Entity\Tag; | 14 | use Wallabag\CoreBundle\Entity\Tag; |
17 | use Wallabag\CoreBundle\Event\EntryDeletedEvent; | 15 | use Wallabag\CoreBundle\Event\EntryDeletedEvent; |
@@ -141,7 +139,7 @@ class EntryRestController extends WallabagRestController | |||
141 | 'tags' => $tags, | 139 | 'tags' => $tags, |
142 | 'since' => $since, | 140 | 'since' => $since, |
143 | ], | 141 | ], |
144 | UrlGeneratorInterface::ABSOLUTE_URL | 142 | true |
145 | ) | 143 | ) |
146 | ); | 144 | ); |
147 | 145 | ||
@@ -774,24 +772,6 @@ class EntryRestController extends WallabagRestController | |||
774 | } | 772 | } |
775 | 773 | ||
776 | /** | 774 | /** |
777 | * Shortcut to send data serialized in json. | ||
778 | * | ||
779 | * @param mixed $data | ||
780 | * | ||
781 | * @return JsonResponse | ||
782 | */ | ||
783 | private function sendResponse($data) | ||
784 | { | ||
785 | // https://github.com/schmittjoh/JMSSerializerBundle/issues/293 | ||
786 | $context = new SerializationContext(); | ||
787 | $context->setSerializeNull(true); | ||
788 | |||
789 | $json = $this->get('jms_serializer')->serialize($data, 'json', $context); | ||
790 | |||
791 | return (new JsonResponse())->setJson($json); | ||
792 | } | ||
793 | |||
794 | /** | ||
795 | * Retrieve value from the request. | 775 | * Retrieve value from the request. |
796 | * Used for POST & PATCH on a an entry. | 776 | * Used for POST & PATCH on a an entry. |
797 | * | 777 | * |
diff --git a/src/Wallabag/ApiBundle/Controller/SearchRestController.php b/src/Wallabag/ApiBundle/Controller/SearchRestController.php new file mode 100644 index 00000000..d9f99844 --- /dev/null +++ b/src/Wallabag/ApiBundle/Controller/SearchRestController.php | |||
@@ -0,0 +1,65 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\ApiBundle\Controller; | ||
4 | |||
5 | use Hateoas\Configuration\Route; | ||
6 | use Hateoas\Representation\Factory\PagerfantaFactory; | ||
7 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | ||
8 | use Pagerfanta\Adapter\DoctrineORMAdapter; | ||
9 | use Pagerfanta\Pagerfanta; | ||
10 | use Symfony\Component\HttpFoundation\JsonResponse; | ||
11 | use Symfony\Component\HttpFoundation\Request; | ||
12 | |||
13 | class SearchRestController extends WallabagRestController | ||
14 | { | ||
15 | /** | ||
16 | * Search all entries by term. | ||
17 | * | ||
18 | * @ApiDoc( | ||
19 | * parameters={ | ||
20 | * {"name"="term", "dataType"="string", "required"=false, "format"="any", "description"="Any query term"}, | ||
21 | * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."}, | ||
22 | * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."} | ||
23 | * } | ||
24 | * ) | ||
25 | * | ||
26 | * @return JsonResponse | ||
27 | */ | ||
28 | public function getSearchAction(Request $request) | ||
29 | { | ||
30 | $this->validateAuthentication(); | ||
31 | |||
32 | $term = $request->query->get('term'); | ||
33 | $page = (int) $request->query->get('page', 1); | ||
34 | $perPage = (int) $request->query->get('perPage', 30); | ||
35 | |||
36 | $qb = $this->get('wallabag_core.entry_repository') | ||
37 | ->getBuilderForSearchByUser( | ||
38 | $this->getUser()->getId(), | ||
39 | $term, | ||
40 | null | ||
41 | ); | ||
42 | |||
43 | $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); | ||
44 | $pager = new Pagerfanta($pagerAdapter); | ||
45 | |||
46 | $pager->setMaxPerPage($perPage); | ||
47 | $pager->setCurrentPage($page); | ||
48 | |||
49 | $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); | ||
50 | $paginatedCollection = $pagerfantaFactory->createRepresentation( | ||
51 | $pager, | ||
52 | new Route( | ||
53 | 'api_get_search', | ||
54 | [ | ||
55 | 'term' => $term, | ||
56 | 'page' => $page, | ||
57 | 'perPage' => $perPage, | ||
58 | ], | ||
59 | true | ||
60 | ) | ||
61 | ); | ||
62 | |||
63 | return $this->sendResponse($paginatedCollection); | ||
64 | } | ||
65 | } | ||
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php index 3c7ad0cf..f18b0910 100644 --- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php +++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php | |||
@@ -3,6 +3,7 @@ | |||
3 | namespace Wallabag\ApiBundle\Controller; | 3 | namespace Wallabag\ApiBundle\Controller; |
4 | 4 | ||
5 | use FOS\RestBundle\Controller\FOSRestController; | 5 | use FOS\RestBundle\Controller\FOSRestController; |
6 | use JMS\Serializer\SerializationContext; | ||
6 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; | 7 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
7 | use Symfony\Component\HttpFoundation\JsonResponse; | 8 | use Symfony\Component\HttpFoundation\JsonResponse; |
8 | use Symfony\Component\Security\Core\Exception\AccessDeniedException; | 9 | use Symfony\Component\Security\Core\Exception\AccessDeniedException; |
@@ -64,4 +65,22 @@ class WallabagRestController extends FOSRestController | |||
64 | throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId()); | 65 | throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId()); |
65 | } | 66 | } |
66 | } | 67 | } |
68 | |||
69 | /** | ||
70 | * Shortcut to send data serialized in json. | ||
71 | * | ||
72 | * @param mixed $data | ||
73 | * | ||
74 | * @return JsonResponse | ||
75 | */ | ||
76 | protected function sendResponse($data) | ||
77 | { | ||
78 | // https://github.com/schmittjoh/JMSSerializerBundle/issues/293 | ||
79 | $context = new SerializationContext(); | ||
80 | $context->setSerializeNull(true); | ||
81 | |||
82 | $json = $this->get('jms_serializer')->serialize($data, 'json', $context); | ||
83 | |||
84 | return (new JsonResponse())->setJson($json); | ||
85 | } | ||
67 | } | 86 | } |
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml index c0283e71..06e62c37 100644 --- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml +++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml | |||
@@ -3,6 +3,11 @@ entry: | |||
3 | resource: "WallabagApiBundle:EntryRest" | 3 | resource: "WallabagApiBundle:EntryRest" |
4 | name_prefix: api_ | 4 | name_prefix: api_ |
5 | 5 | ||
6 | search: | ||
7 | type: rest | ||
8 | resource: "WallabagApiBundle:SearchRest" | ||
9 | name_prefix: api_ | ||
10 | |||
6 | tag: | 11 | tag: |
7 | type: rest | 12 | type: rest |
8 | resource: "WallabagApiBundle:TagRest" | 13 | resource: "WallabagApiBundle:TagRest" |
diff --git a/tests/Wallabag/ApiBundle/Controller/SearchRestControllerTest.php b/tests/Wallabag/ApiBundle/Controller/SearchRestControllerTest.php new file mode 100644 index 00000000..fd524639 --- /dev/null +++ b/tests/Wallabag/ApiBundle/Controller/SearchRestControllerTest.php | |||
@@ -0,0 +1,69 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Tests\Wallabag\ApiBundle\Controller; | ||
4 | |||
5 | use Tests\Wallabag\ApiBundle\WallabagApiTestCase; | ||
6 | |||
7 | class SearchRestControllerTest extends WallabagApiTestCase | ||
8 | { | ||
9 | public function testGetSearchWithFullOptions() | ||
10 | { | ||
11 | $this->client->request('GET', '/api/search', [ | ||
12 | 'page' => 1, | ||
13 | 'perPage' => 2, | ||
14 | 'term' => 'entry', // 6 results | ||
15 | ]); | ||
16 | |||
17 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
18 | |||
19 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
20 | |||
21 | $this->assertGreaterThanOrEqual(1, \count($content)); | ||
22 | $this->assertArrayHasKey('items', $content['_embedded']); | ||
23 | $this->assertGreaterThanOrEqual(0, $content['total']); | ||
24 | $this->assertSame(1, $content['page']); | ||
25 | $this->assertSame(2, $content['limit']); | ||
26 | $this->assertGreaterThanOrEqual(1, $content['pages']); | ||
27 | |||
28 | $this->assertArrayHasKey('_links', $content); | ||
29 | $this->assertArrayHasKey('self', $content['_links']); | ||
30 | $this->assertArrayHasKey('first', $content['_links']); | ||
31 | $this->assertArrayHasKey('last', $content['_links']); | ||
32 | |||
33 | foreach (['self', 'first', 'last'] as $link) { | ||
34 | $this->assertArrayHasKey('href', $content['_links'][$link]); | ||
35 | $this->assertContains('term=entry', $content['_links'][$link]['href']); | ||
36 | } | ||
37 | |||
38 | $this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type')); | ||
39 | } | ||
40 | |||
41 | public function testGetSearchWithNoLimit() | ||
42 | { | ||
43 | $this->client->request('GET', '/api/search', [ | ||
44 | 'term' => 'entry', | ||
45 | ]); | ||
46 | |||
47 | $this->assertSame(200, $this->client->getResponse()->getStatusCode()); | ||
48 | |||
49 | $content = json_decode($this->client->getResponse()->getContent(), true); | ||
50 | |||
51 | $this->assertGreaterThanOrEqual(1, \count($content)); | ||
52 | $this->assertArrayHasKey('items', $content['_embedded']); | ||
53 | $this->assertGreaterThanOrEqual(0, $content['total']); | ||
54 | $this->assertSame(1, $content['page']); | ||
55 | $this->assertGreaterThanOrEqual(1, $content['pages']); | ||
56 | |||
57 | $this->assertArrayHasKey('_links', $content); | ||
58 | $this->assertArrayHasKey('self', $content['_links']); | ||
59 | $this->assertArrayHasKey('first', $content['_links']); | ||
60 | $this->assertArrayHasKey('last', $content['_links']); | ||
61 | |||
62 | foreach (['self', 'first', 'last'] as $link) { | ||
63 | $this->assertArrayHasKey('href', $content['_links'][$link]); | ||
64 | $this->assertContains('term=entry', $content['_links'][$link]['href']); | ||
65 | } | ||
66 | |||
67 | $this->assertSame('application/json', $this->client->getResponse()->headers->get('Content-Type')); | ||
68 | } | ||
69 | } | ||