mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-08-06 12:02:45 +03:00
ZIP Imports: Added API examples, finished testing
Also updated some types on a couple of controllers.
This commit is contained in:
@@ -9,12 +9,11 @@ use BookStack\Entities\Repos\ChapterRepo;
|
|||||||
use BookStack\Exceptions\PermissionsException;
|
use BookStack\Exceptions\PermissionsException;
|
||||||
use BookStack\Http\ApiController;
|
use BookStack\Http\ApiController;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class ChapterApiController extends ApiController
|
class ChapterApiController extends ApiController
|
||||||
{
|
{
|
||||||
protected $rules = [
|
protected array $rules = [
|
||||||
'create' => [
|
'create' => [
|
||||||
'book_id' => ['required', 'integer'],
|
'book_id' => ['required', 'integer'],
|
||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
|
@@ -12,7 +12,7 @@ use Illuminate\Http\Request;
|
|||||||
|
|
||||||
class PageApiController extends ApiController
|
class PageApiController extends ApiController
|
||||||
{
|
{
|
||||||
protected $rules = [
|
protected array $rules = [
|
||||||
'create' => [
|
'create' => [
|
||||||
'book_id' => ['required_without:chapter_id', 'integer'],
|
'book_id' => ['required_without:chapter_id', 'integer'],
|
||||||
'chapter_id' => ['required_without:book_id', 'integer'],
|
'chapter_id' => ['required_without:book_id', 'integer'],
|
||||||
|
@@ -23,6 +23,7 @@ class ImportApiController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* List existing ZIP imports visible to the user.
|
* List existing ZIP imports visible to the user.
|
||||||
|
* Requires permission to import content.
|
||||||
*/
|
*/
|
||||||
public function list(): JsonResponse
|
public function list(): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -34,12 +35,18 @@ class ImportApiController extends ApiController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload, validate and store a ZIP import file.
|
* Start a new import from a ZIP file.
|
||||||
* This does not run the import. That is performed via a separate endpoint.
|
* This does not actually run the import since that is performed via the "run" endpoint.
|
||||||
|
* This uploads, validates and stores the ZIP file so it's ready to be imported.
|
||||||
|
*
|
||||||
|
* This "file" parameter must be a BookStack-compatible ZIP file, and this must be
|
||||||
|
* sent via a 'multipart/form-data' type request.
|
||||||
|
*
|
||||||
|
* Requires permission to import content.
|
||||||
*/
|
*/
|
||||||
public function upload(Request $request): JsonResponse
|
public function create(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$this->validate($request, $this->rules()['upload']);
|
$this->validate($request, $this->rules()['create']);
|
||||||
|
|
||||||
$file = $request->file('file');
|
$file = $request->file('file');
|
||||||
|
|
||||||
@@ -57,6 +64,7 @@ class ImportApiController extends ApiController
|
|||||||
* Read details of a pending ZIP import.
|
* Read details of a pending ZIP import.
|
||||||
* The "details" property contains high-level metadata regarding the ZIP import content,
|
* The "details" property contains high-level metadata regarding the ZIP import content,
|
||||||
* and the structure of this will change depending on import "type".
|
* and the structure of this will change depending on import "type".
|
||||||
|
* Requires permission to import content.
|
||||||
*/
|
*/
|
||||||
public function read(int $id): JsonResponse
|
public function read(int $id): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -69,8 +77,9 @@ class ImportApiController extends ApiController
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Run the import process for an uploaded ZIP import.
|
* Run the import process for an uploaded ZIP import.
|
||||||
* The parent_id and parent_type parameters are required when the import type is 'chapter' or 'page'.
|
* The "parent_id" and "parent_type" parameters are required when the import type is "chapter" or "page".
|
||||||
* On success, returns the imported item.
|
* On success, this endpoint returns the imported item.
|
||||||
|
* Requires permission to import content.
|
||||||
*/
|
*/
|
||||||
public function run(int $id, Request $request): JsonResponse
|
public function run(int $id, Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
@@ -92,11 +101,12 @@ class ImportApiController extends ApiController
|
|||||||
return $this->jsonError($message);
|
return $this->jsonError($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($entity);
|
return response()->json($entity->withoutRelations());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a pending ZIP import.
|
* Delete a pending ZIP import from the system.
|
||||||
|
* Requires permission to import content.
|
||||||
*/
|
*/
|
||||||
public function delete(int $id): Response
|
public function delete(int $id): Response
|
||||||
{
|
{
|
||||||
@@ -109,7 +119,7 @@ class ImportApiController extends ApiController
|
|||||||
protected function rules(): array
|
protected function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'upload' => [
|
'create' => [
|
||||||
'file' => ['required', ...AttachmentService::getFileValidationRules()],
|
'file' => ['required', ...AttachmentService::getFileValidationRules()],
|
||||||
],
|
],
|
||||||
'run' => [
|
'run' => [
|
||||||
|
@@ -16,7 +16,7 @@ class ContentPermissionApiController extends ApiController
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected $rules = [
|
protected array $rules = [
|
||||||
'update' => [
|
'update' => [
|
||||||
'owner_id' => ['int'],
|
'owner_id' => ['int'],
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ use Illuminate\Http\Request;
|
|||||||
|
|
||||||
class SearchApiController extends ApiController
|
class SearchApiController extends ApiController
|
||||||
{
|
{
|
||||||
protected $rules = [
|
protected array $rules = [
|
||||||
'all' => [
|
'all' => [
|
||||||
'query' => ['required'],
|
'query' => ['required'],
|
||||||
'page' => ['integer', 'min:1'],
|
'page' => ['integer', 'min:1'],
|
||||||
|
@@ -16,7 +16,7 @@ class RoleApiController extends ApiController
|
|||||||
'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at',
|
'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $rules = [
|
protected array $rules = [
|
||||||
'create' => [
|
'create' => [
|
||||||
'display_name' => ['required', 'string', 'min:3', 'max:180'],
|
'display_name' => ['required', 'string', 'min:3', 'max:180'],
|
||||||
'description' => ['string', 'max:180'],
|
'description' => ['string', 'max:180'],
|
||||||
|
4
dev/api/requests/imports-run.json
Normal file
4
dev/api/requests/imports-run.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"parent_type": "book",
|
||||||
|
"parent_id": 28
|
||||||
|
}
|
10
dev/api/responses/imports-create.json
Normal file
10
dev/api/responses/imports-create.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"type": "chapter",
|
||||||
|
"name": "Pension Providers",
|
||||||
|
"created_by": 1,
|
||||||
|
"size": 2757,
|
||||||
|
"path": "uploads\/files\/imports\/ghnxmS3u9QxLWu82.zip",
|
||||||
|
"updated_at": "2025-07-18T14:50:27.000000Z",
|
||||||
|
"created_at": "2025-07-18T14:50:27.000000Z",
|
||||||
|
"id": 31
|
||||||
|
}
|
23
dev/api/responses/imports-list.json
Normal file
23
dev/api/responses/imports-list.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "IT Department",
|
||||||
|
"size": 618462,
|
||||||
|
"type": "book",
|
||||||
|
"created_by": 1,
|
||||||
|
"created_at": "2024-12-20T18:40:38.000000Z",
|
||||||
|
"updated_at": "2024-12-20T18:40:38.000000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"name": "Clients",
|
||||||
|
"size": 15364,
|
||||||
|
"type": "chapter",
|
||||||
|
"created_by": 1,
|
||||||
|
"created_at": "2025-03-20T12:41:44.000000Z",
|
||||||
|
"updated_at": "2025-03-20T12:41:44.000000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 2
|
||||||
|
}
|
51
dev/api/responses/imports-read.json
Normal file
51
dev/api/responses/imports-read.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "IT Department",
|
||||||
|
"path": "uploads\/files\/imports\/7YOpZ6sGIEbYdRFL.zip",
|
||||||
|
"size": 618462,
|
||||||
|
"type": "book",
|
||||||
|
"created_by": 1,
|
||||||
|
"created_at": "2024-12-20T18:40:38.000000Z",
|
||||||
|
"updated_at": "2024-12-20T18:40:38.000000Z",
|
||||||
|
"details": {
|
||||||
|
"id": 4,
|
||||||
|
"name": "IT Department",
|
||||||
|
"chapters": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Server Systems",
|
||||||
|
"priority": 1,
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"name": "prod-aws-stonehawk",
|
||||||
|
"priority": 0,
|
||||||
|
"attachments": [],
|
||||||
|
"images": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pages": [
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"name": "Member Onboarding Guide",
|
||||||
|
"priority": 0,
|
||||||
|
"attachments": [],
|
||||||
|
"images": [],
|
||||||
|
"tags": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"name": "IT Holiday Party Event",
|
||||||
|
"priority": 2,
|
||||||
|
"attachments": [],
|
||||||
|
"images": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
|
}
|
14
dev/api/responses/imports-run.json
Normal file
14
dev/api/responses/imports-run.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"id": 1067,
|
||||||
|
"book_id": 28,
|
||||||
|
"slug": "pension-providers",
|
||||||
|
"name": "Pension Providers",
|
||||||
|
"description": "Details on the various pension providers that are available",
|
||||||
|
"priority": 7,
|
||||||
|
"created_at": "2025-07-18T14:53:35.000000Z",
|
||||||
|
"updated_at": "2025-07-18T14:53:36.000000Z",
|
||||||
|
"created_by": 1,
|
||||||
|
"updated_by": 1,
|
||||||
|
"owned_by": 1,
|
||||||
|
"default_template_id": null
|
||||||
|
}
|
@@ -89,7 +89,7 @@ Route::put('roles/{id}', [RoleApiController::class, 'update']);
|
|||||||
Route::delete('roles/{id}', [RoleApiController::class, 'delete']);
|
Route::delete('roles/{id}', [RoleApiController::class, 'delete']);
|
||||||
|
|
||||||
Route::get('imports', [ExportControllers\ImportApiController::class, 'list']);
|
Route::get('imports', [ExportControllers\ImportApiController::class, 'list']);
|
||||||
Route::post('imports', [ExportControllers\ImportApiController::class, 'upload']);
|
Route::post('imports', [ExportControllers\ImportApiController::class, 'create']);
|
||||||
Route::get('imports/{id}', [ExportControllers\ImportApiController::class, 'read']);
|
Route::get('imports/{id}', [ExportControllers\ImportApiController::class, 'read']);
|
||||||
Route::post('imports/{id}', [ExportControllers\ImportApiController::class, 'run']);
|
Route::post('imports/{id}', [ExportControllers\ImportApiController::class, 'run']);
|
||||||
Route::delete('imports/{id}', [ExportControllers\ImportApiController::class, 'delete']);
|
Route::delete('imports/{id}', [ExportControllers\ImportApiController::class, 'delete']);
|
||||||
|
@@ -14,7 +14,7 @@ class ImportsApiTest extends TestCase
|
|||||||
|
|
||||||
protected string $baseEndpoint = '/api/imports';
|
protected string $baseEndpoint = '/api/imports';
|
||||||
|
|
||||||
public function test_upload_and_run(): void
|
public function test_create_and_run(): void
|
||||||
{
|
{
|
||||||
$book = $this->entities->book();
|
$book = $this->entities->book();
|
||||||
$zip = ZipTestHelper::zipUploadFromData([
|
$zip = ZipTestHelper::zipUploadFromData([
|
||||||
@@ -44,12 +44,13 @@ class ImportsApiTest extends TestCase
|
|||||||
'name' => 'My API import page',
|
'name' => 'My API import page',
|
||||||
'book_id' => $book->id,
|
'book_id' => $book->id,
|
||||||
]);
|
]);
|
||||||
|
$resp->assertJsonMissingPath('book');
|
||||||
|
|
||||||
$page = Page::query()->where('name', '=', 'My API import page')->first();
|
$page = Page::query()->where('name', '=', 'My API import page')->first();
|
||||||
$this->assertEquals('My api tag', $page->tags()->first()->name);
|
$this->assertEquals('My api tag', $page->tags()->first()->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_upload_validation_error(): void
|
public function test_create_validation_error(): void
|
||||||
{
|
{
|
||||||
$zip = ZipTestHelper::zipUploadFromData([
|
$zip = ZipTestHelper::zipUploadFromData([
|
||||||
'page' => [
|
'page' => [
|
||||||
|
Reference in New Issue
Block a user