Start recycle bin API endpoints: list, restore, delete

This commit is contained in:
julesdevops 2022-04-06 22:57:18 +02:00
parent c30a9d3564
commit 55e52e45fb
5 changed files with 225 additions and 10 deletions

View File

@ -0,0 +1,34 @@
<?php
namespace BookStack\Entities\Repos;
use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Tools\TrashCan;
use BookStack\Facades\Activity;
class DeletionRepo
{
private TrashCan $trashCan;
public function __construct(TrashCan $trashCan)
{
$this->trashCan = $trashCan;
}
public function restore(int $id): int
{
/** @var Deletion $deletion */
$deletion = Deletion::query()->findOrFail($id);
Activity::add(ActivityType::RECYCLE_BIN_RESTORE, $deletion);
return $this->trashCan->restoreFromDeletion($deletion);
}
public function destroy(int $id): int
{
/** @var Deletion $deletion */
$deletion = Deletion::query()->findOrFail($id);
Activity::add(ActivityType::RECYCLE_BIN_DESTROY, $deletion);
return $this->trashCan->destroyFromDeletion($deletion);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace BookStack\Http\Controllers\Api;
use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Repos\DeletionRepo;
use BookStack\Entities\Tools\TrashCan;
class RecycleBinApiController extends ApiController
{
public function __construct()
{
$this->middleware(function ($request, $next) {
$this->checkPermission('settings-manage');
$this->checkPermission('restrictions-manage-all');
return $next($request);
});
}
public function list()
{
return $this->apiListingResponse(Deletion::query(), [
'id',
'deleted_by',
'created_at',
'updated_at',
'deletable_type',
'deletable_id'
]);
}
public function restore(DeletionRepo $deletionRepo, string $id)
{
$restoreCount = $deletionRepo->restore((int) $id);
return response()->json(['restore_count' => $restoreCount]);
}
public function destroy(DeletionRepo $deletionRepo, string $id)
{
$deleteCount = $deletionRepo->destroy((int) $id);
return response()->json(['delete_count' => $deleteCount]);
}
}

View File

@ -5,6 +5,7 @@ namespace BookStack\Http\Controllers;
use BookStack\Actions\ActivityType;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Repos\DeletionRepo;
use BookStack\Entities\Tools\TrashCan;
class RecycleBinController extends Controller
@ -73,12 +74,9 @@ class RecycleBinController extends Controller
*
* @throws \Exception
*/
public function restore(string $id)
public function restore(DeletionRepo $deletionRepo, string $id)
{
/** @var Deletion $deletion */
$deletion = Deletion::query()->findOrFail($id);
$this->logActivity(ActivityType::RECYCLE_BIN_RESTORE, $deletion);
$restoreCount = (new TrashCan())->restoreFromDeletion($deletion);
$restoreCount = $deletionRepo->restore((int) $id);
$this->showSuccessNotification(trans('settings.recycle_bin_restore_notification', ['count' => $restoreCount]));
@ -103,12 +101,9 @@ class RecycleBinController extends Controller
*
* @throws \Exception
*/
public function destroy(string $id)
public function destroy(DeletionRepo $deletionRepo, string $id)
{
/** @var Deletion $deletion */
$deletion = Deletion::query()->findOrFail($id);
$this->logActivity(ActivityType::RECYCLE_BIN_DESTROY, $deletion);
$deleteCount = (new TrashCan())->destroyFromDeletion($deletion);
$deleteCount = $deletionRepo->destroy((int) $id);
$this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount]));

View File

@ -9,6 +9,7 @@ use BookStack\Http\Controllers\Api\ChapterApiController;
use BookStack\Http\Controllers\Api\ChapterExportApiController;
use BookStack\Http\Controllers\Api\PageApiController;
use BookStack\Http\Controllers\Api\PageExportApiController;
use BookStack\Http\Controllers\Api\RecycleBinApiController;
use BookStack\Http\Controllers\Api\SearchApiController;
use BookStack\Http\Controllers\Api\UserApiController;
use Illuminate\Support\Facades\Route;
@ -72,3 +73,7 @@ Route::post('users', [UserApiController::class, 'create']);
Route::get('users/{id}', [UserApiController::class, 'read']);
Route::put('users/{id}', [UserApiController::class, 'update']);
Route::delete('users/{id}', [UserApiController::class, 'delete']);
Route::get('recycle_bin', [RecycleBinApiController::class, 'list']);
Route::put('recycle_bin/{id}', [RecycleBinApiController::class, 'restore']);
Route::delete('recycle_bin/{id}', [RecycleBinApiController::class, 'destroy']);

View File

@ -0,0 +1,136 @@
<?php
namespace Tests\Api;
use BookStack\Entities\Models\Book;
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Deletion;
use BookStack\Entities\Models\Page;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
class RecycleBinApiTest extends TestCase
{
use TestsApi;
protected string $baseEndpoint = '/api/recycle_bin';
protected array $endpointMap = [
['get', '/api/recycle_bin'],
['put', '/api/recycle_bin/1'],
['delete', '/api/recycle_bin/1'],
];
public function test_settings_manage_permission_needed_for_all_endpoints()
{
$editor = $this->getEditor();
$this->giveUserPermissions($editor, ['settings-manage']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
$resp = $this->json($method, $uri);
$resp->assertStatus(403);
$resp->assertJson($this->permissionErrorResponse());
}
}
public function test_restrictions_manage_all_permission_neeed_for_all_endpoints()
{
$editor = $this->getEditor();
$this->giveUserPermissions($editor, ['restrictions-manage-all']);
$this->actingAs($editor);
foreach ($this->endpointMap as [$method, $uri]) {
$resp = $this->json($method, $uri);
$resp->assertStatus(403);
$resp->assertJson($this->permissionErrorResponse());
}
}
public function test_index_endpoint_returns_expected_page()
{
$this->actingAsAuthorizedUser();
$page = Page::query()->first();
$book = Book::query()->whereHas('pages')->whereHas('chapters')->withCount(['pages', 'chapters'])->first();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$this->actingAs($editor)->delete($book->getUrl());
$deletions = Deletion::query()->orderBy('id')->get();
$resp = $this->getJson($this->baseEndpoint);
$expectedData = $deletions
->zip([$page, $book])
->map(function (Collection $data) use ($editor) {
return [
'id' => $data[0]->id,
'deleted_by' => $editor->getKey(),
'created_at' => $data[0]->created_at->toJson(),
'updated_at' => $data[0]->updated_at->toJson(),
'deletable_type' => $data[1]->getMorphClass(),
'deletable_id' => $data[1]->getKey()
];
});
$resp->assertJson([
'data' => $expectedData->values()->all(),
'total' => 2
]);
}
public function test_restore_endpoint()
{
$this->actingAsAuthorizedUser();
$page = Page::query()->first();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$page->refresh();
$deletion = Deletion::query()->orderBy('id')->first();
$this->assertDatabaseHas('pages', [
'id' => $page->getKey(),
'deleted_at' => $page->deleted_at
]);
$this->putJson($this->baseEndpoint . '/' . $deletion->getKey());
$this->assertDatabaseHas('pages', [
'id' => $page->getKey(),
'deleted_at' => null
]);
}
public function test_destroy_endpoint()
{
$this->actingAsAuthorizedUser();
$page = Page::query()->first();
$editor = $this->getEditor();
$this->actingAs($editor)->delete($page->getUrl());
$page->refresh();
$deletion = Deletion::query()->orderBy('id')->first();
$this->assertDatabaseHas('pages', [
'id' => $page->getKey(),
'deleted_at' => $page->deleted_at
]);
$this->deleteJson($this->baseEndpoint . '/' . $deletion->getKey());
$this->assertDatabaseMissing('pages', ['id' => $page->getKey()]);
}
private function actingAsAuthorizedUser()
{
$editor = $this->getEditor();
$this->giveUserPermissions($editor, ['restrictions-manage-all']);
$this->giveUserPermissions($editor, ['settings-manage']);
$this->actingAs($editor);
}
}