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
6 changes: 3 additions & 3 deletions app/Http/Controllers/BlogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@ public function show(Blog $blog)
{
abort_unless($blog->is_published, 404);

$blog->load('user:id,name,username,image_path');
$blog->load('user:id,name,username,image_path,is_verified');
$blog->increment('views');

$reactionsCount = $blog->reactions()->count();

$reactors = $blog->reactions()
->with(['user:id,name,username,image_path,institution'])
->with(['user:id,name,username,image_path,institution,is_verified'])
->latest('id')
->limit(50)
->get()
Expand All @@ -71,7 +71,7 @@ public function show(Blog $blog)
->values();

$comments = $blog->comments()
->with(['user:id,name,username,image_path,institution'])
->with(['user:id,name,username,image_path,institution,is_verified'])
->latest()
->get();

Expand Down
6 changes: 3 additions & 3 deletions app/Http/Controllers/ChatController.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public function index(Request $request)

// Fetch last X messages in chronological order
$messages = ChatMessage::with([
'user:id,name,username,image_path,institution',
'user:id,name,username,image_path,institution,is_verified',
'user.roles:id,name',
'reactions.user:id,name,username,image_path,institution',
'reactions.user:id,name,username,image_path,institution,is_verified',
'reactions.user.roles:id,name',
])
->latest('id')
Expand Down Expand Up @@ -243,7 +243,7 @@ public function store(Request $request)
// Log without failing response
}

$message->loadMissing(['user:id,name,username,image_path,institution', 'user.roles:id,name']);
$message->loadMissing(['user:id,name,username,image_path,institution,is_verified', 'user.roles:id,name']);

return response()->json([
'id' => $message->id,
Expand Down
10 changes: 5 additions & 5 deletions app/Http/Controllers/ForumController.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public function index(Request $request): Response
$postsQuery = ForumPost::query()
->when(! ($myPosts && auth()->check()), fn ($q) => $q->approved())
->with([
'user:id,name,username,image_path,institution',
'user:id,name,username,image_path,institution,is_verified',
'subject:id,name,course,slug',
'node:id,name,slug',
])
Expand Down Expand Up @@ -93,16 +93,16 @@ public function show(Request $request, ForumPost $post): Response
}

$post->load([
'user:id,name,username,image_path,institution',
'user:id,name,username,image_path,institution,is_verified',
'subject:id,name,course,slug',
'node:id,name,slug',
]);

$answers = $post->directAnswers()
->with([
'user:id,name,username,image_path,institution',
'user:id,name,username,image_path,institution,is_verified',
'replies' => fn ($q) => $q->with([
'user:id,name,username,image_path,institution',
'user:id,name,username,image_path,institution,is_verified',
'parent:id,user_id',
'parent.user:id,name,username',
])->orderBy('created_at', 'asc'),
Expand Down Expand Up @@ -149,7 +149,7 @@ public function show(Request $request, ForumPost $post): Response
$upvoters = ForumVote::where('voteable_type', ForumPost::class)
->where('voteable_id', $post->id)
->where('value', 1)
->with('user:id,name,username,image_path,institution')
->with('user:id,name,username,image_path,institution,is_verified')
->latest()
->get()
->pluck('user')
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/NodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function show(Subject $subject, $path)
: null;

$upvoters = $node->upvotes()
->with(['user:id,name,username,image_path,institution', 'user.roles:id,name'])
->with('user:id,name,username,image_path,institution,is_verified')
->latest('id')
->limit(50)
->get()
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/ResourceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function show($id)
$completionsCount = ResourceCompletion::where('resource_id', $id)->count();

$completers = ResourceCompletion::where('resource_id', $id)
->with(['user:id,name,username,image_path,institution'])
->with(['user:id,name,username,image_path,institution,is_verified'])
->latest()
->take(10)
->get()
Expand Down
11 changes: 5 additions & 6 deletions app/Http/Controllers/UserProfileController.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ public function show(string $username)
: false;

$appreciators = $user->appreciators()
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution'])
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution', 'users.is_verified'])
->inRandomOrder()
->take(30)
->get();

$appreciating = $user->appreciatingUsers()
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution'])
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution', 'users.is_verified'])
->inRandomOrder()
->take(30)
->get();
Expand Down Expand Up @@ -180,8 +180,8 @@ public function show(string $username)
// Suggested / Discover community members: 2 contributors + 2 general users
$contributorUsers = User::where('id', '!=', $user->id)
->whereNotNull('username')
->whereHas('roles')
->select(['id', 'name', 'username', 'institution', 'image_path', 'about'])
->where('is_verified', true)
->select(['id', 'name', 'username', 'institution', 'image_path', 'about', 'is_verified'])
->inRandomOrder()
->take(2)
->get();
Expand All @@ -191,7 +191,7 @@ public function show(string $username)

$randomUsers = User::whereNotIn('id', $excludedIds)
->whereNotNull('username')
->select(['id', 'name', 'username', 'institution', 'image_path', 'about'])
->select(['id', 'name', 'username', 'institution', 'image_path', 'about', 'is_verified'])
->inRandomOrder()
->take($remainingNeeded)
->get();
Expand All @@ -211,7 +211,6 @@ public function show(string $username)
'github' => $user->github,
'created_at' => $user->created_at?->format('M Y') ?? '2026',
'is_verified' => $user->is_verified,
'is_staff' => $user->is_verified,
],
'stats' => [
'questionsCount' => $questionsCount,
Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/User/StoreUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public function rules(): array
'name' => ['required', 'string', 'max:255', new CleanText],
'username' => ['nullable', 'string', 'min:3', 'max:30', 'regex:/^[a-zA-Z0-9_]+$/', 'unique:users,username', new CleanText],
'email' => ['required', 'email', 'unique:users,email'],
'is_verified' => ['sometimes', 'boolean'],
'role' => ['nullable', 'string'],
'permissions' => ['nullable', 'array'],
'permissions.*' => ['string', 'exists:permissions,name'],
Expand Down
1 change: 1 addition & 0 deletions app/Http/Requests/User/UpdateUserRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public function rules(): array
'facebook' => ['sometimes', 'nullable', 'string', 'max:255'],
'instagram' => ['sometimes', 'nullable', 'string', 'max:255'],
'github' => ['sometimes', 'nullable', 'string', 'max:255'],
'is_verified' => ['sometimes', 'boolean'],
'role' => ['sometimes', 'nullable', 'string'],
'permissions' => ['sometimes', 'nullable', 'array'],
'permissions.*' => ['string', 'exists:permissions,name'],
Expand Down
2 changes: 1 addition & 1 deletion app/Models/ChatMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public function getFormattedReactions(?int $currentUserId = null): array
'image_url' => $r->user->image_url,
'image_path' => $r->user->image_path,
'institution' => $r->user->institution,
'is_verified' => (bool) ($r->user->is_verified || ($r->user->relationLoaded('roles') && $r->user->roles->isNotEmpty())),
'is_verified' => (bool) ($r->user->is_verified ?? false),
'roles' => $r->user->relationLoaded('roles') ? $r->user->roles->pluck('name')->toArray() : [],
];
})->filter()->values()->toArray(),
Expand Down
11 changes: 3 additions & 8 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* @property string $name
* @property string $email
* @property Carbon|null $email_verified_at
* @property bool $is_verified
* @property string $password
* @property string|null $remember_token
* @property Carbon|null $created_at
Expand All @@ -41,6 +42,7 @@ class User extends Authenticatable
'username',
'email',
'receive_emails',
'is_verified',
'google_id',
'email_verified_at',
'banned_until',
Expand All @@ -65,17 +67,9 @@ protected static function booted(): void

protected $appends = [
'image_url',
'is_verified',
'is_banned',
];

public function getIsVerifiedAttribute(): bool
{
return $this->relationLoaded('roles')
? $this->roles->isNotEmpty()
: $this->roles()->exists();
}

public function isBanned(): bool
{
return $this->banned_until !== null && $this->banned_until->isFuture();
Expand Down Expand Up @@ -109,6 +103,7 @@ protected function casts(): array
'banned_until' => 'datetime',
'password' => 'hashed',
'receive_emails' => 'boolean',
'is_verified' => 'boolean',
];
}

Expand Down
11 changes: 11 additions & 0 deletions database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,21 @@ public function definition(): array
'email' => fake()->unique()->safeEmail(),
'google_id' => fake()->unique()->uuid(),
'email_verified_at' => now(),
'is_verified' => false,
'remember_token' => Str::random(10),
];
}

/**
* Indicate that the user is verified.
*/
public function verified(): static
{
return $this->state(fn (array $attributes) => [
'is_verified' => true,
]);
}

/**
* Indicate that the model's email address should be unverified.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_verified')->default(false)->after('email_verified_at');
});

$modelHasRolesTable = config('permission.table_names.model_has_roles', 'model_has_roles');
$modelMorphKey = config('permission.column_names.model_morph_key', 'model_id');

if (Schema::hasTable($modelHasRolesTable)) {
$userIdsWithRoles = DB::table($modelHasRolesTable)
->where(function ($query) {
$query->where('model_type', User::class)
->orWhere('model_type', 'App\Models\User');
})
->distinct()
->pluck($modelMorphKey);

if ($userIdsWithRoles->isNotEmpty()) {
DB::table('users')
->whereIn('id', $userIdsWithRoles)
->update(['is_verified' => true]);
}
}
}

/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('is_verified');
});
}
};
1 change: 1 addition & 0 deletions database/seeders/AdminSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public function run(): void
[
'name' => config('app.admin_name', 'ADMIN'),
'email_verified_at' => now(),
'is_verified' => true,
]
);

Expand Down
6 changes: 1 addition & 5 deletions resources/js/components/UserListItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const props = withDefaults(
institution?: string | null;
title?: string | null;
is_verified?: boolean;
roles?: Array<{ id?: number; name: string }>;
};
theme?: 'indigo' | 'emerald' | 'rose';
subtitle?: string | null;
Expand All @@ -30,10 +29,7 @@ const profileUrl = computed(() => {
});

const isVerified = computed(() => {
return Boolean(
props.user.is_verified ??
(props.user.roles && props.user.roles.length > 0),
);
return Boolean(props.user.is_verified);
});

const avatarBgClass = computed(() => {
Expand Down
3 changes: 3 additions & 0 deletions resources/js/components/admin/UserRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { LogIn, Pencil, Trash2, Ban } from 'lucide-vue-next';
import { ref } from 'vue';
import ChatBanModal from '@/components/ChatBanModal.vue';
import StatusBadge from '@/components/StatusBadge.vue';
import VerifiedBadge from '@/components/VerifiedBadge.vue';
import { usePermissions } from '@/lib/usePermissions';

const { can } = usePermissions();
Expand Down Expand Up @@ -81,6 +82,8 @@ const deleteUser = (id: number) => {
{{ user.name }}
</span>

<VerifiedBadge v-if="user.is_verified" />

<span
v-if="user.id === userId"
class="inline-flex items-center rounded bg-indigo-50 px-1.5 py-0.5 text-[10px] font-bold text-indigo-700 uppercase dark:bg-indigo-500/10 dark:text-indigo-400"
Expand Down
5 changes: 5 additions & 0 deletions resources/js/pages/Forum/Index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import EmptyState from '@/components/EmptyState.vue';
import ForumVoteButtons from '@/components/forum/ForumVoteButtons.vue';
import Pagination from '@/components/Pagination.vue';
import PwaInstallPrompt from '@/components/PwaInstallPrompt.vue';
import VerifiedBadge from '@/components/VerifiedBadge.vue';
import { useAuth } from '@/lib/useAuth';
import { formatTimeAgo } from '@/lib/useDate';

Expand All @@ -29,6 +30,7 @@ interface User {
image_path?: string | null;
image_url?: string | null;
institution?: string | null;
is_verified?: boolean;
}

interface NodeItem {
Expand Down Expand Up @@ -758,6 +760,9 @@ const timeAgo = formatTimeAgo;
}}</span>
</div>
<span>{{ post.user.name }}</span>
<VerifiedBadge
v-if="post.user.is_verified"
/>
</Link>
<span
v-else
Expand Down
7 changes: 7 additions & 0 deletions resources/js/pages/Forum/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import ForumVoteButtons from '@/components/forum/ForumVoteButtons.vue';
import ImageViewerModal from '@/components/ImageViewerModal.vue';
import Pagination from '@/components/Pagination.vue';
import UserListItem from '@/components/UserListItem.vue';
import VerifiedBadge from '@/components/VerifiedBadge.vue';
import { useAuth } from '@/lib/useAuth';
import { getCsrfToken } from '@/lib/useCsrf';
import { formatTimeAgo } from '@/lib/useDate';
Expand All @@ -41,6 +42,7 @@ interface User {
image_path?: string | null;
image_url?: string | null;
institution?: string | null;
is_verified?: boolean;
}

interface Subject {
Expand Down Expand Up @@ -755,6 +757,7 @@ function parseMentions(
<span v-else>{{ post.user.name.charAt(0) }}</span>
</div>
<span>{{ post.user.name }}</span>
<VerifiedBadge v-if="post.user.is_verified" />
</Link>
<span
v-else
Expand Down Expand Up @@ -1047,6 +1050,7 @@ function parseMentions(
<span class="text-xs sm:text-sm">{{
answer.user.name
}}</span>
<VerifiedBadge v-if="answer.user.is_verified" />
</Link>
<span
v-else
Expand Down Expand Up @@ -1212,6 +1216,9 @@ function parseMentions(
}}</span>
</div>
<span>{{ reply.user.name }}</span>
<VerifiedBadge
v-if="reply.user.is_verified"
/>
</Link>
<span
v-else
Expand Down
Loading