Skip to content

Commit

Permalink
Deleting threads from users (#1166)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
franbarbalopez and driesvints authored Nov 20, 2024
1 parent 049b35e commit e8cab52
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 4 deletions.
16 changes: 16 additions & 0 deletions app/Http/Controllers/Admin/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Http\Requests\BanRequest;
use App\Jobs\BanUser;
use App\Jobs\DeleteUser;
use App\Jobs\DeleteUserThreads;
use App\Jobs\UnbanUser;
use App\Models\User;
use App\Policies\UserPolicy;
Expand Down Expand Up @@ -39,6 +40,10 @@ public function ban(BanRequest $request, User $user): RedirectResponse

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

if ($request->willDeleteThreads()) {
$this->dispatchSync(new DeleteUserThreads($user));
}

$this->success($user->name().' was banned!');

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

return redirect()->route('admin.users');
}

public function deleteThreads(User $user): RedirectResponse
{
$this->authorize(UserPolicy::DELETE, $user);

$this->dispatchSync(new DeleteUserThreads($user));

$this->success($user->name().' threads were deleted!');

return redirect()->route('admin.users');
}
}
6 changes: 6 additions & 0 deletions app/Http/Requests/BanRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ public function rules(): array
{
return [
'reason' => 'required|string',
'delete_threads' => 'boolean',
];
}

public function reason(): string
{
return $this->get('reason');
}

public function willDeleteThreads(): bool
{
return $this->boolean('delete_threads');
}
}
15 changes: 15 additions & 0 deletions app/Jobs/DeleteUserThreads.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Jobs;

use App\Models\User;

final class DeleteUserThreads
{
public function __construct(private User $user) {}

public function handle(): void
{
$this->user->deleteThreads();
}
}
10 changes: 9 additions & 1 deletion resources/views/admin/users.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
{{ $user->createdAt()->format('j M Y H:i:s') }}
</x-tables.table-data>

<x-tables.table-data class="text-center w-10">
<x-tables.table-data class="text-center w-18">
<a href="{{ route('profile', $user->username()) }}" class="text-lio-600 hover:text-lio-800">
<x-heroicon-o-user-circle class="w-5 h-5 inline" />
</a>
Expand All @@ -81,6 +81,14 @@
<x-modal identifier="deleteUser{{ $user->getKey() }}" :action="route('admin.users.delete', $user->username())" title="Delete {{ $user->username() }}">
<p>Deleting this user will remove their account and any related content like threads & replies. This cannot be undone.</p>
</x-modal>

<button title="Delete {{ $user->name() }} threads." @click="activeModal = 'deleteUserThreads{{ $user->getKey() }}'" class="text-red-600 hover:text-red-800">
<x-heroicon-o-archive-box-x-mark class="w-5 h-5 inline" />
</button>

<x-modal identifier="deleteUserThreads{{ $user->getKey() }}" :action="route('admin.users.threads.delete', $user->username())" title="Delete {{ $user->username() }} threads">
<p>All the threads from this user will be deleted. This cannot be undone.</p>
</x-modal>
@endcan
</x-tables.table-data>
</tr>
Expand Down
5 changes: 4 additions & 1 deletion resources/views/users/profile.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,11 @@ class="w-full bg-center bg-gray-800 h-60 container mx-auto"
type="update"
>
<p>Banning this user will prevent them from logging in, posting threads and replying to threads.</p>
<div class="mt-4">
<div class="mt-4 space-y-4">
<x-forms.inputs.textarea name="reason" placeholder="Provide a reason for banning this user..." required />
<x-forms.inputs.checkbox name="delete_threads" id="delete_threads">
Delete threads
</x-forms.inputs.checkbox>
</div>
</x-modal>
@endif
Expand Down
2 changes: 2 additions & 0 deletions routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@
Route::put('users/{username}/unban', [UsersController::class, 'unban'])->name('.users.unban');
Route::delete('users/{username}', [UsersController::class, 'delete'])->name('.users.delete');

Route::delete('users/{username}/threads', [UsersController::class, 'deleteThreads'])->name('.users.threads.delete');

// Articles
Route::put('articles/{article}/approve', [AdminArticlesController::class, 'approve'])->name('.articles.approve');
Route::put('articles/{article}/disapprove', [AdminArticlesController::class, 'disapprove'])->name('.articles.disapprove');
Expand Down
28 changes: 26 additions & 2 deletions tests/Feature/AdminTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,24 @@
assertCanBanUsers();
});

test('admins can ban a user and delete their threads', function () {
$this->loginAsAdmin();

assertCanBanUsersAndDeleteThreads();
});

test('moderators can ban a user', function () {
$this->loginAsModerator();

assertCanBanUsers();
});

test('moderators can ban a user and delete their threads', function () {
$this->loginAsModerator();

assertCanBanUsersAndDeleteThreads();
});

test('admins can unban a user', function () {
$this->loginAsAdmin();

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

test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason'])
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => false])
->assertRedirect('/user/'.$user->username());

test()->assertDatabaseMissing('users', ['id' => $user->id(), 'banned_at' => null]);
test()->assertDatabaseHas('users', ['id' => $user->id(), 'banned_reason' => 'A good reason']);
}

function assertCanBanUsersAndDeleteThreads()
{
$user = User::factory()->create(['name' => 'Freek Murze']);

test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => true])
->assertRedirect('/user/'.$user->username());

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

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

test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason'])
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason', 'delete_threads' => fake()->boolean()])
->assertForbidden();
}
21 changes: 21 additions & 0 deletions tests/Integration/Jobs/DeleteUserThreadsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

use App\Jobs\DeleteUserThreads;
use App\Models\Thread;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Tests\TestCase;

uses(TestCase::class);
uses(DatabaseMigrations::class);

test('we can delete an user threads', function () {
$user = User::factory()->create();

Thread::factory()->for($user, 'authorRelation')->count(5)->create();

$this->loginAsAdmin();
$this->dispatch(new DeleteUserThreads($user));

$this->assertDatabaseMissing('threads', ['author_id' => $user->id()]);
});

0 comments on commit e8cab52

Please sign in to comment.