diff --git a/app/Search/SearchApiController.php b/app/Search/SearchApiController.php index f74763fb9..35816ac14 100644 --- a/app/Search/SearchApiController.php +++ b/app/Search/SearchApiController.php @@ -9,6 +9,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Http\ApiController; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Collection; class SearchApiController extends ApiController { @@ -50,17 +51,14 @@ class SearchApiController extends ApiController { $this->validate($request, $this->rules['all']); - $options = SearchOptions::fromString($request->input('query') ?? ''); - $page = intval($request->input('page', '0')) ?: 1; - $count = min(intval($request->input('count', '0')) ?: 20, 100); - + [$options, $page, $count] = $this->getRequestData($request); $results = $this->searchRunner->searchEntities($options, 'all', $page, $count); return $this->resultsResponse($results, $options); } /** - * Run a search query against the contents of a single book: its pages and chapters. + * Run a search query against the contents of a single book, searching its pages and chapters. * Takes the same input as the 'all' endpoint, and the same input as the search box * shown within a book in the BookStack interface. * @@ -71,10 +69,7 @@ class SearchApiController extends ApiController { $this->validate($request, $this->rules['book']); - $options = SearchOptions::fromString($request->input('query') ?? ''); - $page = intval($request->input('page', '0')) ?: 1; - $count = min(intval($request->input('count', '0')) ?: 20, 100); - + [$options, $page, $count] = $this->getRequestData($request); $results = $this->searchRunner->searchBook(intval($id), $options, $page, $count); return $this->resultsResponse($results, $options); @@ -91,19 +86,28 @@ class SearchApiController extends ApiController { $this->validate($request, $this->rules['chapter']); - $options = SearchOptions::fromString($request->input('query') ?? ''); - $page = intval($request->input('page', '0')) ?: 1; - $count = min(intval($request->input('count', '0')) ?: 20, 100); - + [$options, $page, $count] = $this->getRequestData($request); $results = $this->searchRunner->searchChapter(intval($id), $options, $page, $count); return $this->resultsResponse($results, $options); } + /** + * Get common search request data. + * @return array{0: SearchOptions, 1: int, 2: int} + */ + protected function getRequestData(Request $request): array + { + $options = SearchOptions::fromString($request->input('query') ?? ''); + $page = intval($request->input('page', '0')) ?: 1; + $count = min(intval($request->input('count', '0')) ?: 20, 100); + return [$options, $page, $count]; + } + /** * Format a set of search results into the standard API response shape. * - * @param array{total: int, results: \Illuminate\Support\Collection} $results + * @param array{total: int, results: Collection} $results */ protected function resultsResponse(array $results, SearchOptions $options): JsonResponse { diff --git a/dev/api/responses/search-book.json b/dev/api/responses/search-book.json index 2ad896416..93587f73e 100644 --- a/dev/api/responses/search-book.json +++ b/dev/api/responses/search-book.json @@ -58,35 +58,7 @@ "order": 0 } ] - }, - { - "name": "How advanced are cats?", - "id": 362, - "slug": "how-advanced-are-cats", - "book_id": 13, - "chapter_id": 73, - "draft": false, - "template": false, - "created_at": "2020-11-29T21:55:07.000000Z", - "updated_at": "2021-11-14T16:02:39.000000Z", - "type": "page", - "url": "https://example.com/books/big-cats/page/how-advanced-are-cats", - "book": { - "id": 13, - "name": "Big Cats", - "slug": "big-cats" - }, - "chapter": { - "id": 73, - "name": "A chapter for bigger cats", - "slug": "a-chapter-for-bigger-cats" - }, - "preview_html": { - "name": "How advanced are cats?", - "content": "cats are some of the most advanced animals in the world." - }, - "tags": [] } ], - "total": 3 + "total": 2 } diff --git a/dev/api/responses/search-chapter.json b/dev/api/responses/search-chapter.json index a7a5765bd..55c277a4f 100644 --- a/dev/api/responses/search-chapter.json +++ b/dev/api/responses/search-chapter.json @@ -43,23 +43,23 @@ "name": "How advanced are cats?", "id": 362, "slug": "how-advanced-are-cats", - "book_id": 13, - "chapter_id": 73, + "book_id": 1, + "chapter_id": 75, "draft": false, "template": false, "created_at": "2020-11-29T21:55:07.000000Z", "updated_at": "2021-11-14T16:02:39.000000Z", "type": "page", - "url": "https://example.com/books/big-cats/page/how-advanced-are-cats", + "url": "https://example.com/books/cats/page/how-advanced-are-cats", "book": { - "id": 13, - "name": "Big Cats", - "slug": "big-cats" + "id": 1, + "name": "Cats", + "slug": "cats" }, "chapter": { - "id": 73, - "name": "A chapter for bigger cats", - "slug": "a-chapter-for-bigger-cats" + "id": 75, + "name": "A chapter for cats", + "slug": "a-chapter-for-cats" }, "preview_html": { "name": "How advanced are cats?", diff --git a/tests/Api/SearchApiTest.php b/tests/Api/SearchApiTest.php index 4d345e7e1..0870e41ce 100644 --- a/tests/Api/SearchApiTest.php +++ b/tests/Api/SearchApiTest.php @@ -68,9 +68,13 @@ class SearchApiTest extends TestCase $chapter = $book->chapters->first(); $chapter->update(['name' => $uniqueTerm . ' chapter']); $chapter->indexForSearch(); + $page = $book->pages->first(); + $page->update(['name' => $uniqueTerm . ' page']); + $page->indexForSearch(); $resp = $this->getJson("/api/search/book/{$book->id}?query=" . urlencode($uniqueTerm)); - $resp->assertJsonFragment(['name' => $chapter->name, 'type' => 'chapter']); + $resp->assertJsonFragment(['name' => 'MyUniqueBookChapterApiTerm chapter', 'type' => 'chapter']); + $resp->assertJsonFragment(['name' => 'MyUniqueBookChapterApiTerm page', 'type' => 'page']); } public function test_book_endpoint_cannot_be_widened_to_other_types() @@ -86,12 +90,16 @@ class SearchApiTest extends TestCase $page->update(['name' => $uniqueTerm . ' a page inside']); $page->indexForSearch(); + $otherBook = $this->entities->book(); + $otherBook->update(['name' => $uniqueTerm . ' the other book']); + $otherBook->indexForSearch(); + // A book cannot contain a book, so asking for one returns nothing rather than // quietly falling back to searching everything. $resp = $this->getJson("/api/search/book/{$book->id}?query=" . urlencode($uniqueTerm . ' {type:book}')); $resp->assertOk(); $resp->assertJsonPath('total', 0); - $resp->assertJsonMissing(['name' => $book->name]); + $resp->assertDontSee($uniqueTerm); } public function test_chapter_endpoint_limits_results_to_that_chapter() @@ -117,9 +125,10 @@ class SearchApiTest extends TestCase { $this->actingAsApiEditor(); $book = $this->entities->book(); + $chapter = $this->entities->chapter(); $this->getJson("/api/search/book/{$book->id}")->assertStatus(422); - $this->getJson("/api/search/chapter/1")->assertStatus(422); + $this->getJson("/api/search/chapter/{$chapter->id}")->assertStatus(422); } public function test_all_endpoint_returns_entity_url()