Skip to content

Commit a7f7299

Browse files
authored
feat: bulk deletion (#460)
* feat: bulk deletion * fix: docs
1 parent e60b8bb commit a7f7299

File tree

10 files changed

+150
-16
lines changed

10 files changed

+150
-16
lines changed

docs-v2/content/en/api/repositories.md

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -56,21 +56,22 @@ for example: `UserPostRepository` class has the model `UserPost`.
5656

5757
Having this in place you're basically ready for the CRUD actions over posts. You have available the follow endpoints:
5858

59-
| Verb | URI | Action |
60-
| :------------- |:----------------------------- | :-------|
61-
| **GET** | `/api/restify/posts` | index |
62-
| **GET** | `/api/restify/posts/actions` | index actions |
63-
| **GET** | `/api/restify/posts/{post}` | show |
64-
| **GET** | `/api/restify/posts/{post}/actions` | individual actions |
65-
| **POST** | `/api/restify/posts` | store |
66-
| **POST** | `/api/restify/posts/actions?action=actionName` | perform index actions |
67-
| **POST** | `/api/restify/posts/bulk` | store multiple |
68-
| **POST** | `/api/restify/posts/bulk/update` | update multiple |
69-
| **PATCH** | `/api/restify/posts/{post}` | partial update |
70-
| **PUT** | `/api/restify/posts/{post}` | full update |
71-
| **POST** | `/api/restify/posts/{post}` | partial of full update including attachments |
72-
| **POST** | `/api/restify/posts/{post}/actions?action=actionName` | perform index actions |
73-
| **DELETE** | `/api/restify/posts/{post}` | destroy |
59+
| Verb | URI | Action |
60+
|:-----------|:------------------------------------------------------|:---------------------------------------------|
61+
| **GET** | `/api/restify/posts` | index |
62+
| **GET** | `/api/restify/posts/actions` | index actions |
63+
| **GET** | `/api/restify/posts/{post}` | show |
64+
| **GET** | `/api/restify/posts/{post}/actions` | individual actions |
65+
| **POST** | `/api/restify/posts` | store |
66+
| **POST** | `/api/restify/posts/actions?action=actionName` | perform index actions |
67+
| **POST** | `/api/restify/posts/bulk` | store multiple |
68+
| **DELETE** | `/api/restify/posts/bulk/delete` | delete multiple |
69+
| **POST** | `/api/restify/posts/bulk/update` | update multiple |
70+
| **PATCH** | `/api/restify/posts/{post}` | partial update |
71+
| **PUT** | `/api/restify/posts/{post}` | full update |
72+
| **POST** | `/api/restify/posts/{post}` | partial of full update including attachments |
73+
| **POST** | `/api/restify/posts/{post}/actions?action=actionName` | perform index actions |
74+
| **DELETE** | `/api/restify/posts/{post}` | destroy |
7475

7576
<alert>
7677

@@ -662,6 +663,18 @@ Payload:
662663
]
663664
```
664665

666+
### Bulk delete flow
667+
668+
The payload for a bulk delete should contain an array of primary keys for the models you want to delete:
669+
670+
```json
671+
[
672+
1, 10, 15
673+
]
674+
```
675+
676+
These models will be resolved from the database and check for the `deleteBulk` policy permission, in case any of the models isn't allowed to be deleted, no entry will be deleted.
677+
665678
## Force eager loading
666679

667680
However, Laravel Restify [provides eager](/search/) loading based on the query `related` property, you may want to force

routes/api.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
Route::post('/{repository}', \Binaryk\LaravelRestify\Http\Controllers\RepositoryStoreController::class)->name('restify.store');
3737
Route::post('/{repository}/bulk', \Binaryk\LaravelRestify\Http\Controllers\RepositoryStoreBulkController::class)->name('restify.store.bulk');
3838
Route::post('/{repository}/bulk/update', \Binaryk\LaravelRestify\Http\Controllers\RepositoryUpdateBulkController::class)->name('restify.update.bulk');
39+
Route::delete('/{repository}/bulk/delete', \Binaryk\LaravelRestify\Http\Controllers\RepositoryDestroyBulkController::class)->name('restify.destroy.bulk');
3940
Route::get('/{repository}/{repositoryId}', \Binaryk\LaravelRestify\Http\Controllers\RepositoryShowController::class)->name('restify.show');
4041
Route::patch('/{repository}/{repositoryId}', \Binaryk\LaravelRestify\Http\Controllers\RepositoryPatchController::class)->name('restify.patch');
4142
Route::put('/{repository}/{repositoryId}', \Binaryk\LaravelRestify\Http\Controllers\RepositoryUpdateController::class)->name('restify.put');

src/Commands/stubs/policy.stub

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ class {{ class }}
4040
//
4141
}
4242

43+
public function deleteBulk(User $user, {{ model }} $model)
44+
{
45+
//
46+
}
47+
4348
public function delete(User $user, {{ model }} $model)
4449
{
4550
//
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Http\Controllers;
4+
5+
use Binaryk\LaravelRestify\Http\Requests\RepositoryDestroyBulkRequest;
6+
use Binaryk\LaravelRestify\Repositories\Repository;
7+
use Illuminate\Support\Facades\DB;
8+
9+
class RepositoryDestroyBulkController
10+
{
11+
public function __invoke(RepositoryDestroyBulkRequest $request)
12+
{
13+
$collection = DB::transaction(function () use ($request) {
14+
return $request->collect()
15+
->each(function (int|string $key, int $row) use ($request) {
16+
$model = $request->modelQuery($key)->lockForUpdate()->firstOrFail();
17+
18+
/**
19+
* @var Repository $repository
20+
*/
21+
$repository = $request->repositoryWith($model);
22+
23+
return $repository
24+
->allowToDestroyBulk($request)
25+
->deleteBulk(
26+
$request,
27+
$key,
28+
$row
29+
);
30+
});
31+
});
32+
33+
return ok();
34+
}
35+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Http\Requests;
4+
5+
class RepositoryDestroyBulkRequest extends RestifyRequest
6+
{
7+
}

src/Repositories/Repository.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,23 @@ public function updateBulk(RestifyRequest $request, $repositoryId, int $row)
807807
return response()->json();
808808
}
809809

810+
public function deleteBulk(RestifyRequest $request, $repositoryId, int $row)
811+
{
812+
$status = DB::transaction(function () use ($request) {
813+
if (in_array(HasActionLogs::class, class_uses_recursive($this->resource))) {
814+
Restify::actionLog()
815+
->forRepositoryDestroy($this->resource, $request->user())
816+
->save();
817+
}
818+
819+
return $this->resource->delete();
820+
});
821+
822+
static::deleted($status, $request);
823+
824+
return ok(code: 204);
825+
}
826+
810827
public function attach(RestifyRequest $request, $repositoryId, Collection $pivots)
811828
{
812829
$eagerField = $this->authorizeBelongsToMany($request)->belongsToManyField($request);
@@ -919,6 +936,13 @@ public function allowToUpdateBulk(RestifyRequest $request, $payload = null): sel
919936
return $this;
920937
}
921938

939+
public function allowToDestroyBulk(RestifyRequest $request, $payload = null): self
940+
{
941+
$this->authorizeToDeleteBulk($request);
942+
943+
return $this;
944+
}
945+
922946
public function allowToStore(RestifyRequest $request, $payload = null): self
923947
{
924948
static::authorizeToStore($request);

src/Traits/AuthorizableModels.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,11 @@ public function authorizeToUpdateBulk(Request $request)
181181
$this->authorizeTo($request, 'updateBulk');
182182
}
183183

184+
public function authorizeToDeleteBulk(Request $request)
185+
{
186+
$this->authorizeTo($request, 'deleteBulk');
187+
}
188+
184189
/**
185190
* Determine if the current user can update the given resource.
186191
*

src/helpers.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function ok(string $message = null, int $code = 200)
3636
], $code);
3737
}
3838

39-
return response()->json([], 204);
39+
return response()->json([], $code);
4040
}
4141
}
4242

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Controllers;
4+
5+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
6+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostPolicy;
7+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
8+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
9+
use Illuminate\Support\Facades\Gate;
10+
11+
class RepositoryDestroyBulkControllerTest extends IntegrationTest
12+
{
13+
protected function setUp(): void
14+
{
15+
parent::setUp();
16+
17+
$this->authenticate();
18+
}
19+
20+
public function test_basic_bulk_delete_works(): void
21+
{
22+
Gate::policy(Post::class, PostPolicy::class);
23+
24+
$post1 = Post::factory()->create();
25+
$post2 = Post::factory()->create();
26+
$post3 = Post::factory()->create();
27+
28+
$this->withoutExceptionHandling();
29+
30+
$this->deleteJson(PostRepository::to('bulk/delete'), [
31+
$post1->getKey(),
32+
$post2->getKey(),
33+
])->assertOk();
34+
35+
$this->assertModelMissing($post1);
36+
$this->assertModelMissing($post2);
37+
$this->assertModelExists($post3);
38+
}
39+
}

tests/Fixtures/Post/PostPolicy.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ public function update($user, $post)
4646
return $_SERVER['restify.post.update'] ?? true;
4747
}
4848

49+
public function deleteBulk($user, $post)
50+
{
51+
return $_SERVER['restify.post.deleteBulk'] ?? true;
52+
}
53+
4954
public function delete($user, $post)
5055
{
5156
return $_SERVER['restify.post.delete'] ?? true;

0 commit comments

Comments
 (0)