diff --git a/app/Http/Controllers/Admin/AuthController.php b/app/Http/Controllers/Admin/AuthController.php
index 4bfdee2..ce3230a 100644
--- a/app/Http/Controllers/Admin/AuthController.php
+++ b/app/Http/Controllers/Admin/AuthController.php
@@ -81,7 +81,11 @@ public function handleGoogleCallback(Request $request)
Auth::login($user, remember: true);
$request->session()->regenerate();
- $redirect = redirect()->intended(route('profile.edit'));
+ $defaultUrl = $user->username
+ ? route('user.profile', $user->username)
+ : route('profile.edit');
+
+ $redirect = redirect()->intended($defaultUrl);
if ($isNewUser) {
$redirect->with('success', 'Account not found. New account created.');
diff --git a/app/Http/Controllers/BlogController.php b/app/Http/Controllers/BlogController.php
index 88e3e51..9ad2f44 100644
--- a/app/Http/Controllers/BlogController.php
+++ b/app/Http/Controllers/BlogController.php
@@ -62,7 +62,7 @@ public function show(Blog $blog)
$reactionsCount = $blog->reactions()->count();
$reactors = $blog->reactions()
- ->with(['user:id,name,username,image_path,institution', 'user.roles:id,name'])
+ ->with(['user:id,name,username,image_path,institution'])
->latest('id')
->limit(50)
->get()
@@ -71,7 +71,7 @@ public function show(Blog $blog)
->values();
$comments = $blog->comments()
- ->with(['user:id,name,username,image_path,institution', 'user.roles:id,name'])
+ ->with(['user:id,name,username,image_path,institution'])
->latest()
->get();
diff --git a/app/Http/Controllers/ResourceController.php b/app/Http/Controllers/ResourceController.php
index 656bc35..bf4cf79 100644
--- a/app/Http/Controllers/ResourceController.php
+++ b/app/Http/Controllers/ResourceController.php
@@ -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', 'user.roles:id,name'])
+ ->with(['user:id,name,username,image_path,institution'])
->latest()
->take(10)
->get()
diff --git a/app/Http/Controllers/UserProfileController.php b/app/Http/Controllers/UserProfileController.php
index 4a9d859..a4e94cf 100644
--- a/app/Http/Controllers/UserProfileController.php
+++ b/app/Http/Controllers/UserProfileController.php
@@ -21,8 +21,6 @@ public function show(string $username)
$user = User::where('username', $username)
->firstOrFail();
- $user->load('roles:id,name');
-
// Study Completions
$completedResourcesCount = $user->completedResources()->count();
$recentCompletions = $user->completedResources()
@@ -40,7 +38,7 @@ public function show(string $username)
->get();
$blogsCount = $user->blogs()->where('is_published', true)->count();
$totalBlogViews = (int) $user->blogs()->where('is_published', true)->sum('views');
- $totalBlogLikes = BlogReaction::whereIn('blog_id', $user->blogs()->where('is_published', true)->select('id'))->count();
+ $upvotesCount = NodeVote::where('user_id', $user->id)->where('type', 'up')->count();
$sharedResourcesCount = Resource::where('user_id', $user->id)->count();
// Appreciations (Received & Given)
@@ -52,23 +50,45 @@ public function show(string $username)
$appreciators = $user->appreciators()
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution', 'users.title'])
- ->with('roles:id,name')
- ->latest('user_appreciations.id')
- ->take(50)
+ ->inRandomOrder()
+ ->take(30)
->get();
$appreciating = $user->appreciatingUsers()
->select(['users.id', 'users.name', 'users.username', 'users.image_path', 'users.institution', 'users.title'])
- ->with('roles:id,name')
- ->latest('user_appreciations.id')
- ->take(50)
+ ->inRandomOrder()
+ ->take(30)
->get();
- // Recent Community Activities (Uploads, completed topics, comments made, reactions given, upvoted folders, appreciations given)
+ // Recent Community Activities (Folders created, uploads, completed topics, comments made, reactions given, upvoted folders, appreciations given)
+ $recentFolders = Node::where('user_id', $user->id)
+ ->with([
+ 'subject:id,name,slug',
+ 'parent:id,name,slug',
+ 'parent.parent:id,name,slug',
+ ])
+ ->latest('id')
+ ->take(3)
+ ->get()
+ ->map(function ($node) {
+ $url = $this->buildNodeUrl($node);
+
+ return [
+ 'type' => 'folder',
+ 'title' => $node->name,
+ 'subtitle' => $node->subject?->name.($node->parent ? ' · '.$node->parent->name : ''),
+ 'url' => $url,
+ 'created_at' => $node->created_at?->diffForHumans(),
+ 'timestamp' => $node->created_at?->timestamp ?? 0,
+ ];
+ })
+ ->filter()
+ ->values();
+
$recentUploads = Resource::where('user_id', $user->id)
->with(['node:id,name,subject_id', 'node.subject:id,name'])
->latest()
- ->take(4)
+ ->take(3)
->get()
->map(fn ($item) => [
'type' => 'upload',
@@ -77,12 +97,13 @@ public function show(string $username)
'resource_type' => $item->resource_type,
'url' => "/resources/{$item->id}",
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
]);
$recentCompleted = ResourceCompletion::where('user_id', $user->id)
->with(['resource:id,title,node_id', 'resource.node:id,name,subject_id', 'resource.node.subject:id,name'])
->latest()
- ->take(4)
+ ->take(3)
->get()
->map(fn ($item) => [
'type' => 'completion',
@@ -90,26 +111,28 @@ public function show(string $username)
'subtitle' => $item->resource?->node?->subject?->name.' · '.$item->resource?->node?->name,
'url' => $item->resource ? "/resources/{$item->resource->id}" : null,
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
])
->filter(fn ($item) => $item['title'] !== null);
$recentReactions = BlogReaction::where('user_id', $user->id)
->with('blog:id,title,slug')
->latest()
- ->take(4)
+ ->take(3)
->get()
->map(fn ($item) => [
'type' => 'reaction',
'title' => $item->blog?->title,
'url' => $item->blog ? "/blogs/{$item->blog->slug}" : null,
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
])
->filter(fn ($item) => $item['title'] !== null);
$recentComments = BlogComment::where('user_id', $user->id)
->with('blog:id,title,slug')
->latest()
- ->take(4)
+ ->take(3)
->get()
->map(fn ($item) => [
'type' => 'comment',
@@ -117,6 +140,7 @@ public function show(string $username)
'content' => $item->content,
'url' => $item->blog ? "/blogs/{$item->blog->slug}" : null,
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
])
->filter(fn ($item) => $item['title'] !== null);
@@ -128,7 +152,7 @@ public function show(string $username)
'node.parent.parent:id,name,slug',
])
->latest('id')
- ->take(4)
+ ->take(3)
->get()
->map(function ($item) {
$node = $item->node;
@@ -144,6 +168,7 @@ public function show(string $username)
'subtitle' => $node->subject?->name.($node->parent ? ' · '.$node->parent->name : ''),
'url' => $url,
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
];
})
->filter()
@@ -152,7 +177,7 @@ public function show(string $username)
$recentAppreciations = UserAppreciation::where('appreciator_id', $user->id)
->with('user:id,name,username')
->latest('id')
- ->take(4)
+ ->take(3)
->get()
->map(fn ($item) => [
'type' => 'appreciation',
@@ -160,6 +185,7 @@ public function show(string $username)
'username' => $item->user?->username,
'url' => $item->user ? "/u/{$item->user->username}" : null,
'created_at' => $item->created_at?->diffForHumans(),
+ 'timestamp' => $item->created_at?->timestamp ?? 0,
])
->filter(fn ($item) => $item['title'] !== null)
->values();
@@ -169,7 +195,6 @@ public function show(string $username)
->whereNotNull('username')
->whereHas('roles')
->select(['id', 'name', 'username', 'title', 'institution', 'image_path', 'about'])
- ->with('roles:id,name')
->inRandomOrder()
->take(2)
->get();
@@ -180,7 +205,6 @@ public function show(string $username)
$randomUsers = User::whereNotIn('id', $excludedIds)
->whereNotNull('username')
->select(['id', 'name', 'username', 'title', 'institution', 'image_path', 'about'])
- ->with('roles:id,name')
->inRandomOrder()
->take($remainingNeeded)
->get();
@@ -200,13 +224,13 @@ public function show(string $username)
'instagram' => $user->instagram,
'github' => $user->github,
'created_at' => $user->created_at?->format('M Y') ?? '2026',
- 'roles' => $user->roles->pluck('name'),
- 'is_staff' => $user->roles->isNotEmpty(),
+ 'is_verified' => $user->is_verified,
+ 'is_staff' => $user->is_verified,
],
'stats' => [
'completedResourcesCount' => $completedResourcesCount,
'blogsCount' => $blogsCount,
- 'totalBlogLikes' => (int) $totalBlogLikes,
+ 'upvotesCount' => $upvotesCount,
'totalBlogViews' => (int) $totalBlogViews,
'sharedResourcesCount' => $sharedResourcesCount,
],
@@ -218,6 +242,7 @@ public function show(string $username)
'recentCompletions' => $recentCompletions,
'blogs' => $publishedBlogs,
'recentActivities' => [
+ 'folders' => $recentFolders->values(),
'uploads' => $recentUploads->values(),
'completions' => $recentCompleted->values(),
'reactions' => $recentReactions->values(),
diff --git a/app/Models/User.php b/app/Models/User.php
index bda86bd..c86e494 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -64,8 +64,16 @@ protected static function booted(): void
protected $appends = [
'image_url',
+ 'is_verified',
];
+ public function getIsVerifiedAttribute(): bool
+ {
+ return $this->relationLoaded('roles')
+ ? $this->roles->isNotEmpty()
+ : $this->roles()->exists();
+ }
+
public function getImageUrlAttribute()
{
if (! $this->image_path) {
diff --git a/resources/js/components/UserListItem.vue b/resources/js/components/UserListItem.vue
index 0954ac8..937d50b 100644
--- a/resources/js/components/UserListItem.vue
+++ b/resources/js/components/UserListItem.vue
@@ -1,6 +1,6 @@
+
+
+
- {{ stats.totalBlogLikes }}
+ {{ stats.upvotesCount }}
- Likes Gained
+ Upvotes Given
"{{ item.content }}"
আমাদের অনুমোদিত প্রমোটর, কিউরেটর, মডারেটর, - রাইটার এবং ডেভেলপারদের অবদানকে সম্মান জানাতে - আমাদের অফিশিয়াল + রাইটার এবং ডেভেলপারদের প্রোফাইলে একটি অফিশিয়াল + Verified Contributor Badge + যুক্ত হবে এবং তাদের অবদানকে সম্মান জানাতে আমাদের + অফিশিয়াল get(route('auth.google.callback')); - $response->assertRedirect(route('profile.edit')); + $user = User::where('email', 'newuser@example.com')->first(); + $response->assertRedirect(route('user.profile', $user->username)); $response->assertSessionHas('success', 'Account not found. New account created.'); $this->assertAuthenticated(); $this->assertDatabaseHas('users', [ diff --git a/tests/Feature/UserProfileTest.php b/tests/Feature/UserProfileTest.php index 7a82779..3f95f9f 100644 --- a/tests/Feature/UserProfileTest.php +++ b/tests/Feature/UserProfileTest.php @@ -158,9 +158,52 @@ $response->assertOk(); $response->assertInertia(fn ($page) => $page ->component('User/Show') + ->where('stats.upvotesCount', 1) ->has('recentActivities.upvotes', 1) ->where('recentActivities.upvotes.0.title', 'Differentiation Master Notes') ->where('recentActivities.upvotes.0.subtitle', 'Higher Math · Calculus') ->where('recentActivities.upvotes.0.url', '/higher-math/calculus/diff-notes') ); }); + +test('user model computes is_verified attribute based on role presence', function () { + $regularUser = User::factory()->create(); + expect($regularUser->is_verified)->toBeFalse(); + + $role = Role::firstOrCreate(['name' => 'editor', 'guard_name' => 'web']); + $staffUser = User::factory()->create(); + $staffUser->assignRole($role); + + expect($staffUser->is_verified)->toBeTrue(); + expect($staffUser->toArray())->toHaveKey('is_verified', true); +}); + +test('public profile returns created folders in recent activities', function () { + $user = User::factory()->create(['username' => 'folder_creator']); + + $subject = Subject::create([ + 'name' => 'Physics', + 'slug' => 'physics', + 'course' => 'hsc', + 'tailwind_format' => 'bg-blue-500', + 'icon' => 'atom', + ]); + + Node::create([ + 'user_id' => $user->id, + 'subject_id' => $subject->id, + 'name' => 'Thermodynamics Chapter', + 'slug' => 'thermodynamics-chapter', + ]); + + $response = $this->get('/u/folder_creator'); + + $response->assertOk(); + $response->assertInertia(fn ($page) => $page + ->component('User/Show') + ->has('recentActivities.folders', 1) + ->where('recentActivities.folders.0.title', 'Thermodynamics Chapter') + ->where('recentActivities.folders.0.subtitle', 'Physics') + ->where('recentActivities.folders.0.url', '/physics/thermodynamics-chapter') + ); +});