diff --git a/app/Http/Controllers/Admin/UsersController.php b/app/Http/Controllers/Admin/UsersController.php index 6db078a67..dd7766937 100644 --- a/app/Http/Controllers/Admin/UsersController.php +++ b/app/Http/Controllers/Admin/UsersController.php @@ -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; @@ -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()); diff --git a/app/Http/Requests/BanRequest.php b/app/Http/Requests/BanRequest.php new file mode 100644 index 000000000..df65e1943 --- /dev/null +++ b/app/Http/Requests/BanRequest.php @@ -0,0 +1,25 @@ + 'required|string', + ]; + } + + public function reason(): string + { + return $this->get('reason'); + } +} diff --git a/app/Jobs/BanUser.php b/app/Jobs/BanUser.php index 9c780a632..54c4fd364 100644 --- a/app/Jobs/BanUser.php +++ b/app/Jobs/BanUser.php @@ -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(); } } diff --git a/app/Jobs/UnbanUser.php b/app/Jobs/UnbanUser.php index 842e3ba74..566789ba4 100644 --- a/app/Jobs/UnbanUser.php +++ b/app/Jobs/UnbanUser.php @@ -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(); } } diff --git a/app/Models/User.php b/app/Models/User.php index 268890299..db674bd06 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -49,6 +49,7 @@ final class User extends Authenticatable implements MustVerifyEmail 'type', 'remember_token', 'bio', + 'banned_reason', ]; /** @@ -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; diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index fe52d3316..742858a84 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -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(), diff --git a/database/migrations/2022_10_15_150405_add_banned_reason_to_users_table.php b/database/migrations/2022_10_15_150405_add_banned_reason_to_users_table.php new file mode 100644 index 000000000..70d4688c5 --- /dev/null +++ b/database/migrations/2022_10_15_150405_add_banned_reason_to_users_table.php @@ -0,0 +1,15 @@ +text('banned_reason')->after('banned_at')->nullable(); + }); + } +}; diff --git a/database/schema/mysql-schema.dump b/database/schema/mysql-schema.dump index ee61e76f4..0b8c4e1c7 100644 --- a/database/schema/mysql-schema.dump +++ b/database/schema/mysql-schema.dump @@ -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`), @@ -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 */; @@ -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`), @@ -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); diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index a5f9f09c3..5cfb8de83 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -104,6 +104,17 @@ class="w-full bg-center bg-gray-800 h-60" @endif @endcan + + @if ($user->bannedReason()) + @can(App\Policies\UserPolicy::BAN, $user) +
+

Banned reason:

+ + {{ $user->bannedReason() }} + +
+ @endcan + @endif @@ -227,6 +238,9 @@ class="w-full bg-center bg-gray-800 h-60" type="update" >

Banning this user will prevent them from logging in, posting threads and replying to threads.

+
+ +
@endif @endcan diff --git a/resources/views/users/settings/profile.blade.php b/resources/views/users/settings/profile.blade.php index 3d7b5e47a..1b43c2aaf 100644 --- a/resources/views/users/settings/profile.blade.php +++ b/resources/views/users/settings/profile.blade.php @@ -24,7 +24,7 @@
- + {{ Auth::user()->bio() }} diff --git a/tests/Feature/AdminTest.php b/tests/Feature/AdminTest.php index fc7fedae6..560beb269 100644 --- a/tests/Feature/AdminTest.php +++ b/tests/Feature/AdminTest.php @@ -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()]); @@ -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() @@ -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() @@ -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(); } diff --git a/tests/Integration/Jobs/BanUserTest.php b/tests/Integration/Jobs/BanUserTest.php index c53ac8359..985977d6c 100644 --- a/tests/Integration/Jobs/BanUserTest.php +++ b/tests/Integration/Jobs/BanUserTest.php @@ -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'); }); diff --git a/tests/Integration/Jobs/UnbanUserTest.php b/tests/Integration/Jobs/UnbanUserTest.php index 94153b003..76fcfe8f9 100644 --- a/tests/Integration/Jobs/UnbanUserTest.php +++ b/tests/Integration/Jobs/UnbanUserTest.php @@ -16,4 +16,5 @@ $unbannedUser = $user->fresh(); expect($unbannedUser->isBanned())->toBeFalse(); + expect($unbannedUser->bannedReason())->toBeNull(); });