Skip to content

Deleting threads from users #1166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()]);
});