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()