From 2d1225d63ed2a5afb4f20ff53e7b1f093397b2e6 Mon Sep 17 00:00:00 2001 From: AbdulrahmanReda70 Date: Thu, 30 Oct 2025 00:25:34 +0300 Subject: [PATCH 1/2] 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 2/2] 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