diff --git a/app/Http/Controllers/AboutUsController.php b/app/Http/Controllers/AboutUsController.php index 7366d6c9..675e14b4 100644 --- a/app/Http/Controllers/AboutUsController.php +++ b/app/Http/Controllers/AboutUsController.php @@ -13,7 +13,6 @@ public function index() $users = Cache::remember('about_us_info', now()->addHours(24), function () { return User::where('is_verified', true) ->select(['id', 'name', 'username', 'title', 'about', 'institution', 'image_path', 'priority']) - ->with('roles:id,name') ->get() ->map(fn (User $user) => [ 'id' => $user->id, @@ -24,7 +23,6 @@ public function index() 'institution' => $user->institution, 'image_url' => $user->image_url, 'priority' => (int) $user->priority, - 'roles' => $user->roles->map(fn ($role) => ['name' => $role->name])->all(), ]) ->all(); }); diff --git a/app/Http/Controllers/Admin/ChatSettingsController.php b/app/Http/Controllers/Admin/ChatSettingsController.php index 0c555b27..bff737ce 100644 --- a/app/Http/Controllers/Admin/ChatSettingsController.php +++ b/app/Http/Controllers/Admin/ChatSettingsController.php @@ -192,8 +192,8 @@ public function clearReports(Request $request) public function updateUserBan(Request $request, User $user) { - if ($user->can('view admin')) { - return back()->with('error', 'Staff and Admin accounts cannot be suspended.'); + if ($user->is_verified) { + return back()->with('error', 'Verified accounts cannot be suspended.'); } $validated = $request->validate([ diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index d41d0804..7b2f420d 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -37,7 +37,7 @@ public function index(Request $request) $query->has('roles'); } elseif ($role === 'student') { $query->doesntHave('roles'); - } elseif (in_array($role, ['admin', 'editor', 'manager'])) { + } elseif (in_array($role, ['admin', 'editor'])) { $query->whereHas('roles', fn ($q) => $q->where('name', $role)); } } diff --git a/app/Http/Controllers/ChatController.php b/app/Http/Controllers/ChatController.php index 09cf4d41..a1f6358f 100644 --- a/app/Http/Controllers/ChatController.php +++ b/app/Http/Controllers/ChatController.php @@ -387,7 +387,7 @@ public function report(Request $request) if ($autoBanEnabled && ! empty($reportedUserId) && $threshold > 0) { $reportedUser = User::find($reportedUserId); - if ($reportedUser && ! $reportedUser->can('view admin')) { + if ($reportedUser && ! $reportedUser->is_verified) { // Count reports keyed to the specific resolved message (or content snapshot) $totalReportsForMessage = Report::where('reported_user_id', $reportedUser->id) ->where('reportable_type', ChatMessage::class) diff --git a/app/Http/Controllers/ResourceController.php b/app/Http/Controllers/ResourceController.php index 1d1e4c65..0eea9a30 100644 --- a/app/Http/Controllers/ResourceController.php +++ b/app/Http/Controllers/ResourceController.php @@ -99,6 +99,13 @@ public function ensureCanSubmit(int $incomingCount = 1): void return; } + if ($user?->isBanned()) { + $bannedUntilFormatted = $user->banned_until->diffForHumans(); + throw ValidationException::withMessages([ + 'banned' => "আপনি সাময়িকভাবে ব্যান রয়েছেন ({$bannedUntilFormatted} পর্যন্ত)। এই সময়ে কোনো রিসোর্স আপলোড বা পরিবর্তন করা যাবে না।", + ]); + } + if ($user?->created_at && $user->created_at->diffInHours(now()) < 24) { throw ValidationException::withMessages([ 'account_age' => 'আপনার অ্যাকাউন্টটি নতুন। স্প্যাম রোধে অ্যাকাউন্ট তৈরির ২৪ ঘণ্টা পর রিসোর্স আপলোড করা যাবে।', diff --git a/database/migrations/2026_10_13_150437_remove_manager_role.php b/database/migrations/2026_10_13_150437_remove_manager_role.php new file mode 100644 index 00000000..15a88ab6 --- /dev/null +++ b/database/migrations/2026_10_13_150437_remove_manager_role.php @@ -0,0 +1,23 @@ +delete(); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Role::findOrCreate('manager', 'web'); + } +}; diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 85e9ad9e..37d73f0e 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -20,7 +20,6 @@ public function run(): void $admin = Role::findOrCreate('admin'); $editor = Role::findOrCreate('editor'); - $manager = Role::findOrCreate('manager'); /* * Grants access to the admin panel. @@ -113,12 +112,5 @@ public function run(): void ]); // Editors can access the admin panel by default. // Content-specific permissions are assigned individually to each editor. - - $manager->syncPermissions([ - 'view admin', - ]); - // Managers can access the admin panel. - // Additional manager permissions can be added here in the future if needed. - } } diff --git a/resources/js/components/StatusBadge.vue b/resources/js/components/StatusBadge.vue index e73ea131..c95baea4 100644 --- a/resources/js/components/StatusBadge.vue +++ b/resources/js/components/StatusBadge.vue @@ -30,7 +30,6 @@ const PRESETS: Record = { // Roles admin: { variant: 'rose', label: 'Admin' }, editor: { variant: 'purple', label: 'Editor' }, - manager: { variant: 'amber', label: 'Staff' }, staff: { variant: 'amber', label: 'Staff' }, // Blogs diff --git a/resources/js/components/UserCard.vue b/resources/js/components/UserCard.vue index 7be834a9..7c84b83e 100644 --- a/resources/js/components/UserCard.vue +++ b/resources/js/components/UserCard.vue @@ -2,7 +2,6 @@ import { Link } from '@inertiajs/vue3'; import { ArrowRight, GraduationCap } from 'lucide-vue-next'; import { computed } from 'vue'; -import StatusBadge from '@/components/StatusBadge.vue'; const props = defineProps<{ member: { @@ -13,7 +12,6 @@ const props = defineProps<{ about?: string | null; institution?: string | null; image_url?: string | null; - roles?: Array<{ name: string }>; }; }>(); @@ -61,14 +59,6 @@ const profileUrl = computed(() => { - -
- -
-

{ checked/unchecked explicitly above. -

-
- Manager -
-
- Dashboard view. Basic view access with no - modification capabilities. -
-
diff --git a/resources/js/pages/platform/AboutUs.vue b/resources/js/pages/platform/AboutUs.vue index 8aa4a895..78b79a54 100644 --- a/resources/js/pages/platform/AboutUs.vue +++ b/resources/js/pages/platform/AboutUs.vue @@ -3,10 +3,6 @@ import { Head, Link } from '@inertiajs/vue3'; import { computed } from 'vue'; import UserCard from '@/components/UserCard.vue'; -interface Role { - name: string; -} - interface Member { id: number; name: string; @@ -16,31 +12,12 @@ interface Member { institution?: string | null; image_url?: string | null; priority?: number; - roles?: Role[]; } const props = defineProps<{ users?: Member[]; }>(); -const getRoleRank = (member: Member): number => { - const roles = member.roles || []; - - if (roles.some((r) => r.name === 'admin')) { - return 1; - } - - if (roles.some((r) => r.name === 'editor')) { - return 2; - } - - if (roles.length > 0) { - return 3; - } - - return 4; -}; - const sortedMembers = computed(() => { const list = [...(props.users || [])]; @@ -53,15 +30,7 @@ const sortedMembers = computed(() => { return priorityB - priorityA; } - // 2. Role rank (admin -> editor -> staff -> others) - const rankA = getRoleRank(a); - const rankB = getRoleRank(b); - - if (rankA !== rankB) { - return rankA - rankB; - } - - // 3. Stable tie-breaker by id ascending + // 2. Stable tie-breaker by id ascending return a.id - b.id; }); }); diff --git a/tests/Feature/AdminUserListFilterTest.php b/tests/Feature/AdminUserListFilterTest.php index ed0f0613..478774fc 100644 --- a/tests/Feature/AdminUserListFilterTest.php +++ b/tests/Feature/AdminUserListFilterTest.php @@ -10,7 +10,6 @@ Permission::findOrCreate('view users', 'web'); $adminRole = Role::findOrCreate('admin', 'web'); $editorRole = Role::findOrCreate('editor', 'web'); - $managerRole = Role::findOrCreate('manager', 'web'); $adminRole->syncPermissions(Permission::all()); }); @@ -37,17 +36,17 @@ $admin->assignRole('admin'); $student1 = User::factory()->create(['name' => 'Student Alice']); $student2 = User::factory()->create(['name' => 'Student Charlie']); - $editor = User::factory()->create(['name' => 'Editor Dave']); - $editor->assignRole('editor'); - $manager = User::factory()->create(['name' => 'Manager Eve']); - $manager->assignRole('manager'); + $editor1 = User::factory()->create(['name' => 'Editor Dave']); + $editor1->assignRole('editor'); + $editor2 = User::factory()->create(['name' => 'Editor Eve']); + $editor2->assignRole('editor'); $response = $this->actingAs($admin)->get('/admin/users?role=staff'); $response->assertStatus(200); $response->assertInertia(fn (Assert $page) => $page ->component('admin/users/Index') - ->has('users.data', 3) // admin + editor + manager + ->has('users.data', 3) // admin + editor1 + editor2 ->where('filters.role', 'staff') ->where('counts.staff', 3) ->where('counts.all', 5) diff --git a/tests/Feature/ChatAutoBanTest.php b/tests/Feature/ChatAutoBanTest.php index cd79f142..56f0337c 100644 --- a/tests/Feature/ChatAutoBanTest.php +++ b/tests/Feature/ChatAutoBanTest.php @@ -112,6 +112,41 @@ expect($targetUser->fresh()->isBanned())->toBeFalse(); }); +test('auto ban is not triggered for verified users', function () { + AppSetting::set('global_chat_auto_ban_enabled', true, 'boolean'); + AppSetting::set('global_chat_auto_ban_reports_threshold', 2, 'integer'); + + $verifiedUser = User::factory()->create(['is_verified' => true]); + $reporter1 = User::factory()->create(); + $reporter2 = User::factory()->create(); + + $this->actingAs($reporter1)->postJson(route('chat.reports.store'), [ + 'reported_user_id' => $verifiedUser->id, + 'message_content' => 'some reportable text', + ])->assertStatus(201); + + $this->actingAs($reporter2)->postJson(route('chat.reports.store'), [ + 'reported_user_id' => $verifiedUser->id, + 'message_content' => 'some reportable text', + ])->assertStatus(201); + + expect($verifiedUser->fresh()->isBanned())->toBeFalse(); +}); + +test('verified accounts cannot be manually suspended', function () { + $admin = User::factory()->create(); + $admin->assignRole('admin'); + + $verifiedUser = User::factory()->create(['is_verified' => true]); + + $response = $this->actingAs($admin)->post(route('admin.chat.users.ban', $verifiedUser), [ + 'banned_until' => now()->addDays(3)->toDateString(), + ]); + + $response->assertSessionHas('error', 'Verified accounts cannot be suspended.'); + expect($verifiedUser->fresh()->isBanned())->toBeFalse(); +}); + test('profanity filter allows legitimate words containing substrings while blocking actual banned words and obfuscations', function () { AppSetting::set('global_chat_profanity_filter_enabled', true, 'boolean'); AppSetting::set('global_chat_banned_words', 'badword, toxic, spammer'); diff --git a/tests/Feature/PublicResourceUploadTest.php b/tests/Feature/PublicResourceUploadTest.php index 0f15093c..9ae987be 100644 --- a/tests/Feature/PublicResourceUploadTest.php +++ b/tests/Feature/PublicResourceUploadTest.php @@ -79,6 +79,27 @@ function createTestSubjectAndNode(bool $isFrozen = false): array ->and($cr->payload['title'])->toBe('My Community Note'); }); +test('banned user cannot upload resources', function () { + [, $node] = createTestSubjectAndNode(); + + $user = User::factory()->create([ + 'created_at' => now()->subDays(5), + 'banned_until' => now()->addDays(7), + ]); + + $response = $this->actingAs($user) + ->post('/resources', [ + 'node_id' => $node->id, + 'resource_type' => 'note', + 'title' => 'Banned User Note', + 'content' => 'Test', + ]); + + $response->assertSessionHasErrors('banned'); + + expect(ResourceChangeRequest::count())->toBe(0); +}); + test('accounts less than 24h old are blocked with Bangla message', function () { [, $node] = createTestSubjectAndNode();