Skip to content

Commit

Permalink
Add ban reason functionality (#927)
Browse files Browse the repository at this point in the history
* Add ban reason functionality

* Remove migration down function

Co-authored-by: Faissal Wahabali <fwahabali@gmail.com>

* Add migration class closing brace back in

* wip

* wip

Co-authored-by: Faissal Wahabali <fwahabali@gmail.com>
Co-authored-by: Dries Vints <dries@vints.io>
  • Loading branch information
3 people committed Nov 2, 2022
1 parent 63f1353 commit 1f2eb9b
Show file tree
Hide file tree
Showing 13 changed files with 119 additions and 9 deletions.
5 changes: 3 additions & 2 deletions app/Http/Controllers/Admin/UsersController.php
Expand Up @@ -4,6 +4,7 @@

use App\Http\Controllers\Controller;
use App\Http\Middleware\VerifyAdmins;
use App\Http\Requests\BanRequest;
use App\Jobs\BanUser;
use App\Jobs\DeleteUser;
use App\Jobs\UnbanUser;
Expand All @@ -30,11 +31,11 @@ public function index()
return view('admin.users', compact('users', 'adminSearch'));
}

public function ban(User $user)
public function ban(BanRequest $request, User $user)
{
$this->authorize(UserPolicy::BAN, $user);

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

$this->success('admin.users.banned', $user->name());

Expand Down
25 changes: 25 additions & 0 deletions app/Http/Requests/BanRequest.php
@@ -0,0 +1,25 @@
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class BanRequest extends FormRequest
{
public function authorize()
{
return true;
}

public function rules()
{
return [
'reason' => 'required|string',
];
}

public function reason(): string
{
return $this->get('reason');
}
}
3 changes: 2 additions & 1 deletion app/Jobs/BanUser.php
Expand Up @@ -7,13 +7,14 @@

final class BanUser
{
public function __construct(private User $user)
public function __construct(private User $user, private $reason)
{
}

public function handle(): void
{
$this->user->banned_at = Carbon::now();
$this->user->banned_reason = $this->reason;
$this->user->save();
}
}
1 change: 1 addition & 0 deletions app/Jobs/UnbanUser.php
Expand Up @@ -13,6 +13,7 @@ public function __construct(private User $user)
public function handle(): void
{
$this->user->banned_at = null;
$this->user->banned_reason = null;
$this->user->save();
}
}
6 changes: 6 additions & 0 deletions app/Models/User.php
Expand Up @@ -49,6 +49,7 @@ final class User extends Authenticatable implements MustVerifyEmail
'type',
'remember_token',
'bio',
'banned_reason',
];

/**
Expand Down Expand Up @@ -111,6 +112,11 @@ public function isBanned(): bool
return ! is_null($this->banned_at);
}

public function bannedReason(): ?string
{
return $this->banned_reason;
}

public function type(): int
{
return (int) $this->type;
Expand Down
1 change: 1 addition & 0 deletions database/factories/UserFactory.php
Expand Up @@ -25,6 +25,7 @@ public function definition(): array
'twitter' => $this->faker->unique()->userName(),
'website' => 'https://laravel.io',
'banned_at' => null,
'banned_reason' => null,
'type' => User::DEFAULT,
'bio' => $this->faker->sentence(),
'email_verified_at' => now()->subDay(),
Expand Down
@@ -0,0 +1,15 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->text('banned_reason')->after('banned_at')->nullable();
});
}
};
21 changes: 20 additions & 1 deletion database/schema/mysql-schema.dump
Expand Up @@ -180,7 +180,7 @@ CREATE TABLE `replies` (
`updated_by` bigint unsigned DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
`deleted_by` int unsigned DEFAULT NULL,
`deleted_reason` text COLLATE utf8mb4_unicode_ci,
`deleted_reason` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`id`),
UNIQUE KEY `replies_uuid_unique` (`uuid`),
KEY `replies_author_id_index` (`author_id`),
Expand All @@ -191,6 +191,22 @@ CREATE TABLE `replies` (
CONSTRAINT `replies_deleted_by_foreign` FOREIGN KEY (`deleted_by`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `spam_reports`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `spam_reports` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`reporter_id` int unsigned NOT NULL,
`spam_type` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`spam_id` bigint unsigned DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `spam_reports_reporter_id_spam_id_spam_type_unique` (`reporter_id`,`spam_id`,`spam_type`),
KEY `spam_reports_spam_type_spam_id_index` (`spam_type`,`spam_id`),
CONSTRAINT `spam_reports_reporter_id_foreign` FOREIGN KEY (`reporter_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `subscriptions`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
Expand Down Expand Up @@ -290,6 +306,7 @@ CREATE TABLE `users` (
`type` smallint unsigned NOT NULL DEFAULT '1',
`bio` varchar(160) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`banned_at` datetime DEFAULT NULL,
`banned_reason` text COLLATE utf8mb4_unicode_ci,
`email_verified_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`),
Expand Down Expand Up @@ -380,3 +397,5 @@ INSERT INTO `migrations` VALUES (71,'2022_06_14_072001_create_blocked_users_tabl
INSERT INTO `migrations` VALUES (72,'2022_07_09_191433_update_articles_table_add_view_count',9);
INSERT INTO `migrations` VALUES (73,'2022_07_29_135113_add__website_to_users_table',9);
INSERT INTO `migrations` VALUES (74,'2022_08_21_215918_add_soft_delete_columns_to_replies_table',10);
INSERT INTO `migrations` VALUES (75,'2022_07_08_145847_create_spam_reports_table',11);
INSERT INTO `migrations` VALUES (76,'2022_10_15_150405_add_banned_reason_to_users_table',11);
14 changes: 14 additions & 0 deletions resources/views/users/profile.blade.php
Expand Up @@ -104,6 +104,17 @@ class="w-full bg-center bg-gray-800 h-60"
</x-buttons.danger-button>
@endif
@endcan

@if ($user->bannedReason())
@can(App\Policies\UserPolicy::BAN, $user)
<div class="mt-2 bg-red-100 text-sm px-4 py-3 border border-red-200 rounded shadow">
<p class="font-semibold">Banned reason:</p>
<span class="text-gray-900">
{{ $user->bannedReason() }}
</span>
</div>
@endcan
@endif
</div>
</div>

Expand Down Expand Up @@ -227,6 +238,9 @@ class="w-full bg-center bg-gray-800 h-60"
type="update"
>
<p>Banning this user will prevent them from logging in, posting threads and replying to threads.</p>
<div class="mt-4">
<x-forms.inputs.textarea name="reason" placeholder="Provide a reason for banning this user..." required />
</div>
</x-modal>
@endif
@endcan
Expand Down
2 changes: 1 addition & 1 deletion resources/views/users/settings/profile.blade.php
Expand Up @@ -24,7 +24,7 @@
<div class="space-y-1">
<x-forms.label for="bio"/>

<x-forms.inputs.textarea name="bio" rows="3" maxlength="160">
<x-forms.inputs.textarea name="bio" maxlength="160">
{{ Auth::user()->bio() }}
</x-forms.inputs.textarea>

Expand Down
29 changes: 26 additions & 3 deletions tests/Feature/AdminTest.php
Expand Up @@ -77,6 +77,28 @@
assertCannotBanModerators();
});

test('admins cannot ban a user without a reason', function () {
$user = User::factory()->create(['name' => 'Freek Murze']);

$this->loginAsAdmin();

$this->put('/admin/users/'.$user->username().'/ban')
->assertRedirectedTo('/');

test()->seeInDatabase('users', ['id' => $user->id(), 'banned_at' => null, 'banned_reason' => null]);
});

test('moderators cannot ban a user without a reason', function () {
$user = User::factory()->create(['name' => 'Freek Murze']);

$this->loginAsModerator();

$this->put('/admin/users/'.$user->username().'/ban')
->assertRedirectedTo('/');

test()->seeInDatabase('users', ['id' => $user->id(), 'banned_at' => null, 'banned_reason' => null]);
});

test('admins can delete a user', function () {
$user = User::factory()->create(['name' => 'Freek Murze']);
$thread = Thread::factory()->create(['author_id' => $user->id()]);
Expand Down Expand Up @@ -344,10 +366,11 @@ function assertCanBanUsers()
{
$user = User::factory()->create(['name' => 'Freek Murze']);

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

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

function assertCanUnbanUsers()
Expand All @@ -357,7 +380,7 @@ function assertCanUnbanUsers()
test()->put('/admin/users/'.$user->username().'/unban')
->assertRedirectedTo('/user/'.$user->username());

test()->seeInDatabase('users', ['id' => $user->id(), 'banned_at' => null]);
test()->seeInDatabase('users', ['id' => $user->id(), 'banned_at' => null, 'banned_reason' => null]);
}

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

test()->put('/admin/users/'.$user->username().'/ban')
test()->put('/admin/users/'.$user->username().'/ban', ['reason' => 'A good reason'])
->assertForbidden();
}
5 changes: 4 additions & 1 deletion tests/Integration/Jobs/BanUserTest.php
Expand Up @@ -10,9 +10,12 @@
it('can ban a user', function () {
$user = $this->createUser(['banned_at' => null]);

$this->dispatch(new BanUser($user));
$reason = 'A good reason';

$this->dispatch(new BanUser($user, $reason));

$bannedUser = $user->fresh();

expect($bannedUser->isBanned())->toBeTrue();
expect($bannedUser->bannedReason())->toBe('A good reason');
});
1 change: 1 addition & 0 deletions tests/Integration/Jobs/UnbanUserTest.php
Expand Up @@ -16,4 +16,5 @@
$unbannedUser = $user->fresh();

expect($unbannedUser->isBanned())->toBeFalse();
expect($unbannedUser->bannedReason())->toBeNull();
});

0 comments on commit 1f2eb9b

Please sign in to comment.