3 declare(strict_types=1);
5 namespace BookStack\Exports\Controllers;
7 use BookStack\Exceptions\ZipImportException;
8 use BookStack\Exceptions\ZipValidationException;
9 use BookStack\Exports\ImportRepo;
10 use BookStack\Http\ApiController;
11 use BookStack\Uploads\AttachmentService;
12 use Illuminate\Http\Request;
13 use Illuminate\Http\JsonResponse;
14 use Illuminate\Http\Response;
16 class ImportApiController extends ApiController
18 public function __construct(
19 protected ImportRepo $imports,
21 $this->middleware('can:content-import');
25 * List existing ZIP imports visible to the user.
27 public function list(): JsonResponse
29 $query = $this->imports->queryVisible();
31 return $this->apiListingResponse($query, [
32 'id', 'name', 'size', 'type', 'created_by', 'created_at', 'updated_at'
37 * Upload, validate and store a ZIP import file.
38 * This does not run the import. That is performed via a separate endpoint.
40 public function upload(Request $request): JsonResponse
42 $this->validate($request, $this->rules()['upload']);
44 $file = $request->file('file');
47 $import = $this->imports->storeFromUpload($file);
48 } catch (ZipValidationException $exception) {
49 $message = "ZIP upload failed with the following validation errors: \n" . $this->formatErrors($exception->errors);
50 return $this->jsonError($message, 422);
53 return response()->json($import);
57 * Read details of a pending ZIP import.
58 * The "details" property contains high-level metadata regarding the ZIP import content,
59 * and the structure of this will change depending on import "type".
61 public function read(int $id): JsonResponse
63 $import = $this->imports->findVisible($id);
65 $import->setAttribute('details', $import->decodeMetadata());
67 return response()->json($import);
71 * Run the import process for an uploaded ZIP import.
72 * The parent_id and parent_type parameters are required when the import type is 'chapter' or 'page'.
73 * On success, returns the imported item.
75 public function run(int $id, Request $request): JsonResponse
77 $import = $this->imports->findVisible($id);
79 $rules = $this->rules()['run'];
81 if ($import->type === 'page' || $import->type === 'chapter') {
82 $rules['parent_type'][] = 'required';
83 $rules['parent_id'][] = 'required';
84 $data = $this->validate($request, $rules);
85 $parent = "{$data['parent_type']}:{$data['parent_id']}";
89 $entity = $this->imports->runImport($import, $parent);
90 } catch (ZipImportException $exception) {
91 $message = "ZIP import failed with the following errors: \n" . $this->formatErrors($exception->errors);
92 return $this->jsonError($message);
95 return response()->json($entity);
99 * Delete a pending ZIP import.
101 public function delete(int $id): Response
103 $import = $this->imports->findVisible($id);
104 $this->imports->deleteImport($import);
106 return response('', 204);
109 protected function rules(): array
113 'file' => ['required', ...AttachmentService::getFileValidationRules()],
116 'parent_type' => ['string', 'in:book,chapter'],
117 'parent_id' => ['int'],
122 protected function formatErrors(array $errors): string
125 foreach ($errors as $key => $error) {
126 if (is_string($key)) {
127 $parts[] = "[{$key}] {$error}";
132 return implode("\n", $parts);