Skip to content
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
2 changes: 0 additions & 2 deletions app/Http/Controllers/AboutUsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
});
Expand Down
4 changes: 2 additions & 2 deletions app/Http/Controllers/Admin/ChatSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/Admin/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions app/Http/Controllers/ResourceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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' => 'আপনার অ্যাকাউন্টটি নতুন। স্প্যাম রোধে অ্যাকাউন্ট তৈরির ২৪ ঘণ্টা পর রিসোর্স আপলোড করা যাবে।',
Expand Down
23 changes: 23 additions & 0 deletions database/migrations/2026_10_13_150437_remove_manager_role.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Spatie\Permission\Models\Role;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Role::where('name', 'manager')->delete();
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Role::findOrCreate('manager', 'web');
}
};
8 changes: 0 additions & 8 deletions database/seeders/RolePermissionSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.

}
}
1 change: 0 additions & 1 deletion resources/js/components/StatusBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const PRESETS: Record<string, PresetConfig> = {
// Roles
admin: { variant: 'rose', label: 'Admin' },
editor: { variant: 'purple', label: 'Editor' },
manager: { variant: 'amber', label: 'Staff' },
staff: { variant: 'amber', label: 'Staff' },

// Blogs
Expand Down
10 changes: 0 additions & 10 deletions resources/js/components/UserCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -13,7 +12,6 @@ const props = defineProps<{
about?: string | null;
institution?: string | null;
image_url?: string | null;
roles?: Array<{ name: string }>;
};
}>();

Expand Down Expand Up @@ -61,14 +59,6 @@ const profileUrl = computed(() => {
</Link>
</div>

<!-- Role Pill -->
<div class="flex items-center justify-center pt-0.5">
<StatusBadge
:status="member.roles?.[0]?.name || 'staff'"
size="xs"
/>
</div>

<!-- Title / Tagline -->
<p
v-if="member.title"
Expand Down
12 changes: 0 additions & 12 deletions resources/js/pages/admin/users/CreateOrEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const roles = [
{ value: '', label: 'Student (No Role)' },
{ value: 'admin', label: 'Admin' },
{ value: 'editor', label: 'Editor' },
{ value: 'manager', label: 'Manager' },
];

const form = useForm({
Expand Down Expand Up @@ -613,17 +612,6 @@ const submitForm = () => {
checked/unchecked explicitly above.
</div>
</div>
<div class="space-y-1 border-l-2 border-amber-500 pl-3">
<div class="text-xs font-bold text-amber-700">
Manager
</div>
<div
class="text-xs leading-relaxed text-slate-600 dark:text-gray-400"
>
Dashboard view. Basic view access with no
modification capabilities.
</div>
</div>
</div>
</div>

Expand Down
33 changes: 1 addition & 32 deletions resources/js/pages/platform/AboutUs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 || [])];

Expand All @@ -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;
});
});
Expand Down
11 changes: 5 additions & 6 deletions tests/Feature/AdminUserListFilterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
});

Expand 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)
Expand Down
35 changes: 35 additions & 0 deletions tests/Feature/ChatAutoBanTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
21 changes: 21 additions & 0 deletions tests/Feature/PublicResourceUploadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
Loading