Also updated some types on a couple of controllers.
use BookStack\Exceptions\PermissionsException;
use BookStack\Http\ApiController;
use Exception;
-use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Http\Request;
class ChapterApiController extends ApiController
{
- protected $rules = [
+ protected array $rules = [
'create' => [
'book_id' => ['required', 'integer'],
'name' => ['required', 'string', 'max:255'],
class PageApiController extends ApiController
{
- protected $rules = [
+ protected array $rules = [
'create' => [
'book_id' => ['required_without:chapter_id', 'integer'],
'chapter_id' => ['required_without:book_id', 'integer'],
/**
* List existing ZIP imports visible to the user.
+ * Requires permission to import content.
*/
public function list(): JsonResponse
{
}
/**
- * Upload, validate and store a ZIP import file.
- * This does not run the import. That is performed via a separate endpoint.
+ * Start a new import from a ZIP file.
+ * 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');
* Read details of a pending ZIP import.
* The "details" property contains high-level metadata regarding the ZIP import content,
* and the structure of this will change depending on import "type".
+ * Requires permission to import content.
*/
public function read(int $id): JsonResponse
{
/**
* 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'.
- * On success, returns the imported item.
+ * The "parent_id" and "parent_type" parameters are required when the import type is "chapter" or "page".
+ * On success, this endpoint returns the imported item.
+ * Requires permission to import content.
*/
public function run(int $id, Request $request): JsonResponse
{
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
{
protected function rules(): array
{
return [
- 'upload' => [
+ 'create' => [
'file' => ['required', ...AttachmentService::getFileValidationRules()],
],
'run' => [
) {
}
- protected $rules = [
+ protected array $rules = [
'update' => [
'owner_id' => ['int'],
class SearchApiController extends ApiController
{
- protected $rules = [
+ protected array $rules = [
'all' => [
'query' => ['required'],
'page' => ['integer', 'min:1'],
'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at',
];
- protected $rules = [
+ protected array $rules = [
'create' => [
'display_name' => ['required', 'string', 'min:3', 'max:180'],
'description' => ['string', 'max:180'],
--- /dev/null
+{
+ "parent_type": "book",
+ "parent_id": 28
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+}
\ No newline at end of file
--- /dev/null
+{
+ "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": []
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "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
+}
\ No newline at end of file
Route::delete('roles/{id}', [RoleApiController::class, 'delete']);
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::post('imports/{id}', [ExportControllers\ImportApiController::class, 'run']);
Route::delete('imports/{id}', [ExportControllers\ImportApiController::class, 'delete']);
protected string $baseEndpoint = '/api/imports';
- public function test_upload_and_run(): void
+ public function test_create_and_run(): void
{
$book = $this->entities->book();
$zip = ZipTestHelper::zipUploadFromData([
'name' => 'My API import page',
'book_id' => $book->id,
]);
+ $resp->assertJsonMissingPath('book');
$page = Page::query()->where('name', '=', 'My API import page')->first();
$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([
'page' => [