From 2d1225d63ed2a5afb4f20ff53e7b1f093397b2e6 Mon Sep 17 00:00:00 2001 From: AbdulrahmanReda70 Date: Thu, 30 Oct 2025 00:25:34 +0300 Subject: [PATCH 01/11] Add avatar refresh button for GitHub-connected users - Add refresh button next to avatar on user profile pages - Button only visible when viewing your own profile - Button only appears if GitHub account is connected - Implement per-user rate limiting (1 request per minute) - Add ProfileController::refresh() method with proper validation - Update avatar component to support showRefresh prop - Fix method name from hasIdenticon() to hasGithubIdenticon() for consistency - Add route for avatar refresh functionality This feature allows users to manually refresh their GitHub avatar without waiting for automatic updates. The rate limiting prevents abuse while providing a better user experience. --- app/Http/Controllers/ProfileController.php | 26 +++++++++++++++++ app/Models/User.php | 2 +- resources/views/components/avatar.blade.php | 32 ++++++++++++++++----- resources/views/users/profile.blade.php | 7 ++++- routes/web.php | 1 + 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 17946ee1f..82d4a8142 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -2,8 +2,10 @@ namespace App\Http\Controllers; +use App\Jobs\UpdateUserIdenticonStatus; use App\Models\User; use Illuminate\Http\Request; +use Illuminate\Support\Facades\RateLimiter; class ProfileController extends Controller { @@ -21,4 +23,28 @@ public function show(Request $request, ?User $user = null) abort(404); } + + public function refresh(Request $request) + { + + $user = $request->user(); + + if (!$user->hasConnectedGitHubAccount()) { + return back()->with('error', 'You need to connect your GitHub account to refresh your avatar.'); + } + + // Rate limiting: 1 request per 1 minute per user + $key = 'avatar-refresh:' . $user->id(); + + if (RateLimiter::tooManyAttempts($key, 1)) { + return back()->with('error', "Please wait 1 minute(s) before refreshing your avatar again."); + } + + // Record this attempt for 1 minutes + RateLimiter::hit($key, 60); + + UpdateUserIdenticonStatus::dispatchSync($user); + + return back()->with('success', 'Avatar refresh queued! Your profile image will be updated shortly.'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 87e841cfc..8fe934860 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -123,7 +123,7 @@ public function hasConnectedGitHubAccount(): bool return ! is_null($this->githubId()); } - public function hasIdenticon(): bool + public function hasGithubIdenticon(): bool { return (bool) $this->github_has_identicon; } diff --git a/resources/views/components/avatar.blade.php b/resources/views/components/avatar.blade.php index db261e951..14daa0005 100644 --- a/resources/views/components/avatar.blade.php +++ b/resources/views/components/avatar.blade.php @@ -1,17 +1,19 @@ @props([ 'user', 'unlinked' => false, + 'showRefresh' => false, ]) githubId() && ! $user->hasIdenticon() +$src = $user->githubId() && ! $user->hasGithubIdenticon() ? sprintf('https://avatars.githubusercontent.com/u/%s', $user->githubId()) : asset('https://laravel.io/images/laravelio-icon-gray.svg'); ?> -@unless ($unlinked) - -@endunless +
+ @unless ($unlinked) + + @endunless merge(['class' => 'bg-gray-50']) }} /> -@unless ($unlinked) - -@endunless + @unless ($unlinked) + + @endunless + + @if ($showRefresh && $user->hasConnectedGitHubAccount()) +
+ @csrf + +
+ @endif +
diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index 304b07ea1..8e9238c85 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -12,7 +12,12 @@ class="w-full bg-center bg-gray-800 h-60 container mx-auto"
- +
diff --git a/routes/web.php b/routes/web.php index 5c7e652f5..cc7abffd3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,6 +70,7 @@ Route::get('user/{username?}', [ProfileController::class, 'show'])->name('profile'); Route::put('users/{username}/block', BlockUserController::class)->name('users.block'); Route::put('users/{username}/unblock', UnblockUserController::class)->name('users.unblock'); +Route::post('/avatar/refresh', [ProfileController::class, 'refresh'])->name('avatar.refresh'); // Notifications Route::view('notifications', 'users.notifications')->name('notifications')->middleware(Authenticate::class); From cd9abe6d9a17a868f093258384f1ec43d4d5e9e3 Mon Sep 17 00:00:00 2001 From: AbdulrahmanReda70 <158822881+AbdulrahmanReda70@users.noreply.github.com> Date: Wed, 29 Oct 2025 21:32:15 +0000 Subject: [PATCH 02/11] Fix code styling --- app/Http/Controllers/ProfileController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 82d4a8142..88154e5e3 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -29,15 +29,15 @@ public function refresh(Request $request) $user = $request->user(); - if (!$user->hasConnectedGitHubAccount()) { + if (! $user->hasConnectedGitHubAccount()) { return back()->with('error', 'You need to connect your GitHub account to refresh your avatar.'); } // Rate limiting: 1 request per 1 minute per user - $key = 'avatar-refresh:' . $user->id(); + $key = 'avatar-refresh:'.$user->id(); if (RateLimiter::tooManyAttempts($key, 1)) { - return back()->with('error', "Please wait 1 minute(s) before refreshing your avatar again."); + return back()->with('error', 'Please wait 1 minute(s) before refreshing your avatar again.'); } // Record this attempt for 1 minutes From c11d9fbb2261c520cfb7e6117c12757ce5a13fc4 Mon Sep 17 00:00:00 2001 From: AbdulrahmanReda70 Date: Thu, 30 Oct 2025 17:27:03 +0300 Subject: [PATCH 03/11] Apply suggested changes: rename method, fix route, and update comment --- app/Http/Controllers/ProfileController.php | 3 +- app/Models/User.php | 2 +- resources/views/components/avatar.blade.php | 54 ++++++++++----------- routes/web.php | 2 +- 4 files changed, 29 insertions(+), 32 deletions(-) diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 88154e5e3..2db9d1832 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -26,7 +26,6 @@ public function show(Request $request, ?User $user = null) public function refresh(Request $request) { - $user = $request->user(); if (! $user->hasConnectedGitHubAccount()) { @@ -40,7 +39,7 @@ public function refresh(Request $request) return back()->with('error', 'Please wait 1 minute(s) before refreshing your avatar again.'); } - // Record this attempt for 1 minutes + // Record this attempt for 1 minute. RateLimiter::hit($key, 60); UpdateUserIdenticonStatus::dispatchSync($user); diff --git a/app/Models/User.php b/app/Models/User.php index 8fe934860..492675ee1 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -123,7 +123,7 @@ public function hasConnectedGitHubAccount(): bool return ! is_null($this->githubId()); } - public function hasGithubIdenticon(): bool + public function hasGitHubIdenticon(): bool { return (bool) $this->github_has_identicon; } diff --git a/resources/views/components/avatar.blade.php b/resources/views/components/avatar.blade.php index 14daa0005..c2059322f 100644 --- a/resources/views/components/avatar.blade.php +++ b/resources/views/components/avatar.blade.php @@ -1,44 +1,42 @@ @props([ - 'user', - 'unlinked' => false, - 'showRefresh' => false, +'user', +'unlinked' => false, +'showRefresh' => false, ]) githubId() && ! $user->hasGithubIdenticon() +$src = $user->githubId() && ! $user->hasGitHubIdenticon() ? sprintf('https://avatars.githubusercontent.com/u/%s', $user->githubId()) : asset('https://laravel.io/images/laravelio-icon-gray.svg'); ?>
@unless ($unlinked) - - @endunless + + @endunless -merge(['class' => 'bg-gray-50']) }} -/> + merge(['class' => 'bg-gray-50']) }} /> - @unless ($unlinked) - + @unless ($unlinked) + @endunless @if ($showRefresh && $user->hasConnectedGitHubAccount()) -
- @csrf - -
+
+ @csrf + +
@endif -
+
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index cc7abffd3..7903f59c7 100644 --- a/routes/web.php +++ b/routes/web.php @@ -70,7 +70,7 @@ Route::get('user/{username?}', [ProfileController::class, 'show'])->name('profile'); Route::put('users/{username}/block', BlockUserController::class)->name('users.block'); Route::put('users/{username}/unblock', UnblockUserController::class)->name('users.unblock'); -Route::post('/avatar/refresh', [ProfileController::class, 'refresh'])->name('avatar.refresh'); +Route::post('avatar/refresh', [ProfileController::class, 'refresh'])->name('avatar.refresh'); // Notifications Route::view('notifications', 'users.notifications')->name('notifications')->middleware(Authenticate::class); From 04e8204f6e27f3deb7de806d849414780a94e2b2 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 31 Oct 2025 13:02:24 +0100 Subject: [PATCH 04/11] wip --- resources/views/components/avatar.blade.php | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/resources/views/components/avatar.blade.php b/resources/views/components/avatar.blade.php index c2059322f..315b80ce5 100644 --- a/resources/views/components/avatar.blade.php +++ b/resources/views/components/avatar.blade.php @@ -1,7 +1,7 @@ @props([ -'user', -'unlinked' => false, -'showRefresh' => false, + 'user', + 'unlinked' => false, + 'showRefresh' => false, ]) @unless ($unlinked) - - @endunless + + @endunless - merge(['class' => 'bg-gray-50']) }} /> + merge(['class' => 'bg-gray-50']) }} /> - @unless ($unlinked) - + @unless ($unlinked) + @endunless @if ($showRefresh && $user->hasConnectedGitHubAccount()) -
- @csrf - -
+
+ @csrf + +
@endif
\ No newline at end of file From 702b862b10aa6d16d54d0d3ed2ed1131061da727 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 31 Oct 2025 13:04:33 +0100 Subject: [PATCH 05/11] wip --- resources/views/users/profile.blade.php | 7 +------ resources/views/users/settings/profile.blade.php | 7 ++++++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index 8e9238c85..304b07ea1 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -12,12 +12,7 @@ class="w-full bg-center bg-gray-800 h-60 container mx-auto"
- +
diff --git a/resources/views/users/settings/profile.blade.php b/resources/views/users/settings/profile.blade.php index c7b88e32e..dcd52ba42 100644 --- a/resources/views/users/settings/profile.blade.php +++ b/resources/views/users/settings/profile.blade.php @@ -41,7 +41,12 @@