mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-06-07 03:14:33 +08:00
Merge branch 'rashadkhan359/development' into development
Some checks failed
analyse-php / build (push) Has been cancelled
lint-js / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-js / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
Some checks failed
analyse-php / build (push) Has been cancelled
lint-js / build (push) Has been cancelled
lint-php / build (push) Has been cancelled
test-js / build (push) Has been cancelled
test-migrations / build (8.1) (push) Has been cancelled
test-migrations / build (8.2) (push) Has been cancelled
test-migrations / build (8.3) (push) Has been cancelled
test-php / build (8.1) (push) Has been cancelled
test-php / build (8.2) (push) Has been cancelled
test-php / build (8.3) (push) Has been cancelled
This commit is contained in:
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace BookStack\Api;
|
namespace BookStack\Api;
|
||||||
|
|
||||||
|
use BookStack\Entities\Models\BookChild;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
|
|
||||||
class ApiEntityListFormatter
|
class ApiEntityListFormatter
|
||||||
{
|
{
|
||||||
@ -20,8 +22,16 @@ class ApiEntityListFormatter
|
|||||||
* @var array<string|int, string|callable>
|
* @var array<string|int, string|callable>
|
||||||
*/
|
*/
|
||||||
protected array $fields = [
|
protected array $fields = [
|
||||||
'id', 'name', 'slug', 'book_id', 'chapter_id', 'draft',
|
'id',
|
||||||
'template', 'priority', 'created_at', 'updated_at',
|
'name',
|
||||||
|
'slug',
|
||||||
|
'book_id',
|
||||||
|
'chapter_id',
|
||||||
|
'draft',
|
||||||
|
'template',
|
||||||
|
'priority',
|
||||||
|
'created_at',
|
||||||
|
'updated_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(array $list)
|
public function __construct(array $list)
|
||||||
@ -62,6 +72,28 @@ class ApiEntityListFormatter
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include parent book/chapter info in the formatted data.
|
||||||
|
*/
|
||||||
|
public function withParents(): self
|
||||||
|
{
|
||||||
|
$this->withField('book', function (Entity $entity) {
|
||||||
|
if ($entity instanceof BookChild && $entity->book) {
|
||||||
|
return $entity->book->only(['id', 'name', 'slug']);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->withField('chapter', function (Entity $entity) {
|
||||||
|
if ($entity instanceof Page && $entity->chapter) {
|
||||||
|
return $entity->chapter->only(['id', 'name', 'slug']);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format the data and return an array of formatted content.
|
* Format the data and return an array of formatted content.
|
||||||
* @return array[]
|
* @return array[]
|
||||||
|
@ -9,21 +9,18 @@ use Illuminate\Http\Request;
|
|||||||
|
|
||||||
class SearchApiController extends ApiController
|
class SearchApiController extends ApiController
|
||||||
{
|
{
|
||||||
protected SearchRunner $searchRunner;
|
|
||||||
protected SearchResultsFormatter $resultsFormatter;
|
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'all' => [
|
'all' => [
|
||||||
'query' => ['required'],
|
'query' => ['required'],
|
||||||
'page' => ['integer', 'min:1'],
|
'page' => ['integer', 'min:1'],
|
||||||
'count' => ['integer', 'min:1', 'max:100'],
|
'count' => ['integer', 'min:1', 'max:100'],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
public function __construct(SearchRunner $searchRunner, SearchResultsFormatter $resultsFormatter)
|
public function __construct(
|
||||||
{
|
protected SearchRunner $searchRunner,
|
||||||
$this->searchRunner = $searchRunner;
|
protected SearchResultsFormatter $resultsFormatter
|
||||||
$this->resultsFormatter = $resultsFormatter;
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,16 +47,16 @@ class SearchApiController extends ApiController
|
|||||||
$this->resultsFormatter->format($results['results']->all(), $options);
|
$this->resultsFormatter->format($results['results']->all(), $options);
|
||||||
|
|
||||||
$data = (new ApiEntityListFormatter($results['results']->all()))
|
$data = (new ApiEntityListFormatter($results['results']->all()))
|
||||||
->withType()->withTags()
|
->withType()->withTags()->withParents()
|
||||||
->withField('preview_html', function (Entity $entity) {
|
->withField('preview_html', function (Entity $entity) {
|
||||||
return [
|
return [
|
||||||
'name' => (string) $entity->getAttribute('preview_name'),
|
'name' => (string) $entity->getAttribute('preview_name'),
|
||||||
'content' => (string) $entity->getAttribute('preview_content'),
|
'content' => (string) $entity->getAttribute('preview_content'),
|
||||||
];
|
];
|
||||||
})->format();
|
})->format();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'total' => $results['total'],
|
'total' => $results['total'],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
GET /api/search?query=cats+{created_by:me}&page=1&count=2
|
GET /api/search?query=cats+{created_by:me}&page=1&count=2
|
||||||
|
@ -8,7 +8,12 @@
|
|||||||
"created_at": "2021-11-14T15:57:35.000000Z",
|
"created_at": "2021-11-14T15:57:35.000000Z",
|
||||||
"updated_at": "2021-11-14T15:57:35.000000Z",
|
"updated_at": "2021-11-14T15:57:35.000000Z",
|
||||||
"type": "chapter",
|
"type": "chapter",
|
||||||
"url": "https://example.com/books/my-book/chapter/a-chapter-for-cats",
|
"url": "https://example.com/books/cats/chapter/a-chapter-for-cats",
|
||||||
|
"book": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Cats",
|
||||||
|
"slug": "cats"
|
||||||
|
},
|
||||||
"preview_html": {
|
"preview_html": {
|
||||||
"name": "A chapter for <strong>cats</strong>",
|
"name": "A chapter for <strong>cats</strong>",
|
||||||
"content": "...once a bunch of <strong>cats</strong> named tony...behaviour of <strong>cats</strong> is unsuitable"
|
"content": "...once a bunch of <strong>cats</strong> named tony...behaviour of <strong>cats</strong> is unsuitable"
|
||||||
@ -26,7 +31,17 @@
|
|||||||
"created_at": "2021-05-15T16:28:10.000000Z",
|
"created_at": "2021-05-15T16:28:10.000000Z",
|
||||||
"updated_at": "2021-11-14T15:56:49.000000Z",
|
"updated_at": "2021-11-14T15:56:49.000000Z",
|
||||||
"type": "page",
|
"type": "page",
|
||||||
"url": "https://example.com/books/my-book/page/the-hows-and-whys-of-cats",
|
"url": "https://example.com/books/cats/page/the-hows-and-whys-of-cats",
|
||||||
|
"book": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Cats",
|
||||||
|
"slug": "cats"
|
||||||
|
},
|
||||||
|
"chapter": {
|
||||||
|
"id": 75,
|
||||||
|
"name": "A chapter for cats",
|
||||||
|
"slug": "a-chapter-for-cats"
|
||||||
|
},
|
||||||
"preview_html": {
|
"preview_html": {
|
||||||
"name": "The hows and whys of <strong>cats</strong>",
|
"name": "The hows and whys of <strong>cats</strong>",
|
||||||
"content": "...people ask why <strong>cats</strong>? but there are...the reason that <strong>cats</strong> are fast are due to..."
|
"content": "...people ask why <strong>cats</strong>? but there are...the reason that <strong>cats</strong> are fast are due to..."
|
||||||
@ -55,7 +70,17 @@
|
|||||||
"created_at": "2020-11-29T21:55:07.000000Z",
|
"created_at": "2020-11-29T21:55:07.000000Z",
|
||||||
"updated_at": "2021-11-14T16:02:39.000000Z",
|
"updated_at": "2021-11-14T16:02:39.000000Z",
|
||||||
"type": "page",
|
"type": "page",
|
||||||
"url": "https://example.com/books/my-book/page/how-advanced-are-cats",
|
"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": {
|
"preview_html": {
|
||||||
"name": "How advanced are <strong>cats</strong>?",
|
"name": "How advanced are <strong>cats</strong>?",
|
||||||
"content": "<strong>cats</strong> are some of the most advanced animals in the world."
|
"content": "<strong>cats</strong> are some of the most advanced animals in the world."
|
||||||
@ -64,4 +89,4 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total": 3
|
"total": 3
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class SearchApiTest extends TestCase
|
|||||||
{
|
{
|
||||||
use TestsApi;
|
use TestsApi;
|
||||||
|
|
||||||
protected $baseEndpoint = '/api/search';
|
protected string $baseEndpoint = '/api/search';
|
||||||
|
|
||||||
public function test_all_endpoint_returns_search_filtered_results_with_query()
|
public function test_all_endpoint_returns_search_filtered_results_with_query()
|
||||||
{
|
{
|
||||||
@ -45,7 +45,7 @@ class SearchApiTest extends TestCase
|
|||||||
$resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
|
$resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
|
||||||
$resp->assertJsonFragment([
|
$resp->assertJsonFragment([
|
||||||
'type' => 'page',
|
'type' => 'page',
|
||||||
'url' => $page->getUrl(),
|
'url' => $page->getUrl(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,10 +57,10 @@ class SearchApiTest extends TestCase
|
|||||||
|
|
||||||
$resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
|
$resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue');
|
||||||
$resp->assertJsonFragment([
|
$resp->assertJsonFragment([
|
||||||
'type' => 'book',
|
'type' => 'book',
|
||||||
'url' => $book->getUrl(),
|
'url' => $book->getUrl(),
|
||||||
'preview_html' => [
|
'preview_html' => [
|
||||||
'name' => 'name with <strong>superuniquevalue</strong> within',
|
'name' => 'name with <strong>superuniquevalue</strong> within',
|
||||||
'content' => 'Description with <strong>superuniquevalue</strong> within',
|
'content' => 'Description with <strong>superuniquevalue</strong> within',
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@ -74,4 +74,46 @@ class SearchApiTest extends TestCase
|
|||||||
$resp = $this->actingAsApiEditor()->get($this->baseEndpoint . '?query=myqueryvalue');
|
$resp = $this->actingAsApiEditor()->get($this->baseEndpoint . '?query=myqueryvalue');
|
||||||
$resp->assertOk();
|
$resp->assertOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_all_endpoint_includes_parent_details_where_visible()
|
||||||
|
{
|
||||||
|
$page = $this->entities->pageWithinChapter();
|
||||||
|
$chapter = $page->chapter;
|
||||||
|
$book = $page->book;
|
||||||
|
|
||||||
|
$page->update(['name' => 'name with superextrauniquevalue within']);
|
||||||
|
$page->indexForSearch();
|
||||||
|
|
||||||
|
$editor = $this->users->editor();
|
||||||
|
$this->actingAsApiEditor();
|
||||||
|
$resp = $this->getJson($this->baseEndpoint . '?query=superextrauniquevalue');
|
||||||
|
$resp->assertJsonFragment([
|
||||||
|
'id' => $page->id,
|
||||||
|
'type' => 'page',
|
||||||
|
'book' => [
|
||||||
|
'id' => $book->id,
|
||||||
|
'name' => $book->name,
|
||||||
|
'slug' => $book->slug,
|
||||||
|
],
|
||||||
|
'chapter' => [
|
||||||
|
'id' => $chapter->id,
|
||||||
|
'name' => $chapter->name,
|
||||||
|
'slug' => $chapter->slug,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->permissions->disableEntityInheritedPermissions($chapter);
|
||||||
|
$this->permissions->setEntityPermissions($page, ['view'], [$editor->roles()->first()]);
|
||||||
|
|
||||||
|
$resp = $this->getJson($this->baseEndpoint . '?query=superextrauniquevalue');
|
||||||
|
$resp->assertJsonPath('data.0.id', $page->id);
|
||||||
|
$resp->assertJsonPath('data.0.book.name', $book->name);
|
||||||
|
$resp->assertJsonMissingPath('data.0.chapter');
|
||||||
|
|
||||||
|
$this->permissions->disableEntityInheritedPermissions($book);
|
||||||
|
|
||||||
|
$resp = $this->getJson($this->baseEndpoint . '?query=superextrauniquevalue');
|
||||||
|
$resp->assertJsonPath('data.0.id', $page->id);
|
||||||
|
$resp->assertJsonMissingPath('data.0.book.name');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user