Skip to content

Commit e8cab52

Browse files
Deleting threads from users (#1166)
* new users threads delete route * new job to delete user threads * add option in admin panel to delete threads * add new logic on controller to delete threads from user and from banning them * delete_threads param validation when banning user * add checkbox to delete threads or not when banning user * added test suite * wip --------- Co-authored-by: Dries Vints <[email protected]>
1 parent 049b35e commit e8cab52

File tree

8 files changed

+99
-4
lines changed

8 files changed

+99
-4
lines changed

app/Http/Controllers/Admin/UsersController.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Http\Requests\BanRequest;
88
use App\Jobs\BanUser;
99
use App\Jobs\DeleteUser;
10+
use App\Jobs\DeleteUserThreads;
1011
use App\Jobs\UnbanUser;
1112
use App\Models\User;
1213
use App\Policies\UserPolicy;
@@ -39,6 +40,10 @@ public function ban(BanRequest $request, User $user): RedirectResponse
3940

4041
$this->dispatchSync(new BanUser($user, $request->get('reason')));
4142

43+
if ($request->willDeleteThreads()) {
44+
$this->dispatchSync(new DeleteUserThreads($user));
45+
}
46+
4247
$this->success($user->name().' was banned!');
4348

4449
return redirect()->route('profile', $user->username());
@@ -65,4 +70,15 @@ public function delete(User $user): RedirectResponse
6570

6671
return redirect()->route('admin.users');
6772
}
73+
74+
public function deleteThreads(User $user): RedirectResponse
75+
{
76+
$this->authorize(UserPolicy::DELETE, $user);
77+
78+
$this->dispatchSync(new DeleteUserThreads($user));
79+
80+
$this->success($user->name().' threads were deleted!');
81+
82+
return redirect()->route('admin.users');
83+
}
6884
}

app/Http/Requests/BanRequest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@ public function rules(): array
1515
{
1616
return [
1717
'reason' => 'required|string',
18+
'delete_threads' => 'boolean',
1819
];
1920
}
2021

2122
public function reason(): string
2223
{
2324
return $this->get('reason');
2425
}
26+
27+
public function willDeleteThreads(): bool
28+
{
29+
return $this->boolean('delete_threads');
30+
}
2531
}

app/Jobs/DeleteUserThreads.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
namespace App\Jobs;
4+
5+
use App\Models\User;
6+
7+
final class DeleteUserThreads
8+
{
9+
public function __construct(private User $user) {}
10+
11+
public function handle(): void
12+
{
13+
$this->user->deleteThreads();
14+
}
15+
}

resources/views/admin/users.blade.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
{{ $user->createdAt()->format('j M Y H:i:s') }}
6969
</x-tables.table-data>
7070

71-
<x-tables.table-data class="text-center w-10">
71+
<x-tables.table-data class="text-center w-18">
7272
<a href="{{ route('profile', $user->username()) }}" class="text-lio-600 hover:text-lio-800">
7373
<x-heroicon-o-user-circle class="w-5 h-5 inline" />
7474
</a>
@@ -81,6 +81,14 @@
8181
<x-modal identifier="deleteUser{{ $user->getKey() }}" :action="route('admin.users.delete', $user->username())" title="Delete {{ $user->username() }}">
8282
<p>Deleting this user will remove their account and any related content like threads & replies. This cannot be undone.</p>
8383
</x-modal>
84+
85+
<button title="Delete {{ $user->name() }} threads." @click="activeModal = 'deleteUserThreads{{ $user->getKey() }}'" class="text-red-600 hover:text-red-800">
86+
<x-heroicon-o-archive-box-x-mark class="w-5 h-5 inline" />
87+
</button>
88+
89+
<x-modal identifier="deleteUserThreads{{ $user->getKey() }}" :action="route('admin.users.threads.delete', $user->username())" title="Delete {{ $user->username() }} threads">
90+
<p>All the threads from this user will be deleted. This cannot be undone.</p>
91+
</x-modal>
8492
@endcan
8593
</x-tables.table-data>
8694
</tr>

resources/views/users/profile.blade.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,11 @@ class="w-full bg-center bg-gray-800 h-60 container mx-auto"
238238
type="update"
239239
>
240240
<p>Banning this user will prevent them from logging in, posting threads and replying to threads.</p>
241-
<div class="mt-4">
241+
<div class="mt-4 space-y-4">
242242
<x-forms.inputs.textarea name="reason" placeholder="Provide a reason for banning this user..." required />
243+
<x-forms.inputs.checkbox name="delete_threads" id="delete_threads">
244+
Delete threads
245+
</x-forms.inputs.checkbox>
243246
</div>
244247
</x-modal>
245248
@endif

routes/web.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@
138138
Route::put('users/{username}/unban', [UsersController::class, 'unban'])->name('.users.unban');
139139
Route::delete('users/{username}', [UsersController::class, 'delete'])->name('.users.delete');
140140

141+
Route::delete('users/{username}/threads', [UsersController::class, 'deleteThreads'])->name('.users.threads.delete');
142+
141143
// Articles
142144
Route::put('articles/{article}/approve', [AdminArticlesController::class, 'approve'])->name('.articles.approve');
143145
Route::put('articles/{article}/disapprove', [AdminArticlesController::class, 'disapprove'])->name('.articles.disapprove');

tests/Feature/AdminTest.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,24 @@
4141
assertCanBanUsers();
4242
});
4343

44+
test('admins can ban a user and delete their threads', function () {
45+
$this->loginAsAdmin();
46+
47+
assertCanBanUsersAndDeleteThreads();
48+
});
49+
4450
test('moderators can ban a user', function () {
4551
$this->loginAsModerator();
4652

4753
assertCanBanUsers();
4854
});
4955

56+
test('moderators can ban a user and delete their threads', function () {
57+
$this->loginAsModerator();
58+
59+
assertCanBanUsersAndDeleteThreads();
60+
});
61+
5062
test('admins can unban a user', function () {
5163
$this->loginAsAdmin();
5264

@@ -366,11 +378,23 @@ function assertCanBanUsers()
366378
{
367379
$user = User::factory()->create(['name' => 'Freek Murze']);
368380

369-
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason'])
381+
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => false])
382+
->assertRedirect('/user/'.$user->username());
383+
384+
test()->assertDatabaseMissing('users', ['id' => $user->id(), 'banned_at' => null]);
385+
test()->assertDatabaseHas('users', ['id' => $user->id(), 'banned_reason' => 'A good reason']);
386+
}
387+
388+
function assertCanBanUsersAndDeleteThreads()
389+
{
390+
$user = User::factory()->create(['name' => 'Freek Murze']);
391+
392+
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => true])
370393
->assertRedirect('/user/'.$user->username());
371394

372395
test()->assertDatabaseMissing('users', ['id' => $user->id(), 'banned_at' => null]);
373396
test()->assertDatabaseHas('users', ['id' => $user->id(), 'banned_reason' => 'A good reason']);
397+
test()->assertDatabaseMissing('threads', ['author_id' => $user->id()]);
374398
}
375399

376400
function assertCanUnbanUsers()
@@ -397,6 +421,6 @@ function assertCannotBanUsersByType(int $type)
397421
{
398422
$user = User::factory()->create(['type' => $type]);
399423

400-
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason'])
424+
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => fake()->boolean()])
401425
->assertForbidden();
402426
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use App\Jobs\DeleteUserThreads;
4+
use App\Models\Thread;
5+
use App\Models\User;
6+
use Illuminate\Foundation\Testing\DatabaseMigrations;
7+
use Tests\TestCase;
8+
9+
uses(TestCase::class);
10+
uses(DatabaseMigrations::class);
11+
12+
test('we can delete an user threads', function () {
13+
$user = User::factory()->create();
14+
15+
Thread::factory()->for($user, 'authorRelation')->count(5)->create();
16+
17+
$this->loginAsAdmin();
18+
$this->dispatch(new DeleteUserThreads($user));
19+
20+
$this->assertDatabaseMissing('threads', ['author_id' => $user->id()]);
21+
});

0 commit comments

Comments
 (0)