From b28d633ea5f10941c5ecdef607c0535e98c91304 Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Tue, 10 Nov 2026 11:08:17 +0000 Subject: [PATCH 1/2] Show repo star counts in the Mobile and Desktop dropdowns The GitHub items in the header dropdowns are now titled mobile-air and desktop, with the repo's star count as the subtext. A new github:sync-repo-stars command fetches both counts once a day and caches them forever, so a failed fetch keeps the last good value and page requests never call GitHub. With nothing cached the subtext falls back to "GitHub". Co-Authored-By: Claude Opus 5.5 --- .../Commands/SyncGitHubRepositoryStars.php | 33 ++++ app/Console/Kernel.php | 6 + app/Support/GitHub.php | 60 +++++- .../navbar/device-dropdowns.blade.php | 10 +- tests/Feature/GitHubRepositoryStarsTest.php | 176 ++++++++++++++++++ 5 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 app/Console/Commands/SyncGitHubRepositoryStars.php create mode 100644 tests/Feature/GitHubRepositoryStarsTest.php diff --git a/app/Console/Commands/SyncGitHubRepositoryStars.php b/app/Console/Commands/SyncGitHubRepositoryStars.php new file mode 100644 index 000000000..f2843b0ed --- /dev/null +++ b/app/Console/Commands/SyncGitHubRepositoryStars.php @@ -0,0 +1,33 @@ +refreshStars(); + + if ($stars === null) { + $failed = true; + $this->error("Failed to fetch stars for {$repository->package}, keeping the last cached count"); + + continue; + } + + $this->info("{$repository->package}: {$stars} stars"); + } + + return $failed ? self::FAILURE : self::SUCCESS; + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 08fe3bb37..958c3b21e 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -24,6 +24,12 @@ protected function schedule(Schedule $schedule): void ->onOneServer() ->runInBackground(); + // Refresh the repository star counts shown in the site header's Mobile and Desktop dropdowns + $schedule->command('github:sync-repo-stars') + ->dailyAt('09:00') + ->onOneServer() + ->runInBackground(); + // Remove Discord Max role for users with expired Max licenses $schedule->command('discord:remove-expired-roles') ->dailyAt('10:30') diff --git a/app/Support/GitHub.php b/app/Support/GitHub.php index 639c107b9..0d0b13a38 100644 --- a/app/Support/GitHub.php +++ b/app/Support/GitHub.php @@ -3,9 +3,13 @@ namespace App\Support; use App\Support\GitHub\Release; +use Illuminate\Http\Client\ConnectionException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Number; +use Illuminate\Support\Str; class GitHub { @@ -20,7 +24,7 @@ class GitHub public const PACKAGE_MOBILE_AIR = 'nativephp/mobile-air'; public function __construct( - private string $package + public readonly string $package ) {} public static function desktop(): static @@ -105,6 +109,60 @@ public function releasesFrom(string $version): Collection )->values(); } + /** + * The repository's star count as last cached by refreshStars(). Never hits + * the GitHub API, so it is safe to call while rendering a page. + */ + public function stars(): ?int + { + return Cache::get($this->getCacheKey('stars')); + } + + /** + * The cached star count in a compact, human form such as "1.2k stars", or + * null when nothing has been cached yet. + */ + public function formattedStars(): ?string + { + $stars = $this->stars(); + + if ($stars === null) { + return null; + } + + return Str::lower(Number::abbreviate($stars, maxPrecision: 1)).' '.Str::plural('star', $stars); + } + + /** + * Fetch the star count from GitHub and cache it with no expiry. A failed + * request leaves the previously cached count in place. + */ + public function refreshStars(): ?int + { + try { + $response = Http::timeout(10) + ->accept('application/vnd.github+json') + ->when(config('services.github.token'), fn ($request, string $token) => $request->withToken($token)) + ->get('https://api.github.com/repos/'.$this->package); + } catch (ConnectionException $e) { + Log::warning('Failed to fetch GitHub star count', ['repository' => $this->package, 'error' => $e->getMessage()]); + + return null; + } + + $stars = $response->json('stargazers_count'); + + if ($response->failed() || ! is_int($stars)) { + Log::warning('Failed to fetch GitHub star count', ['repository' => $this->package, 'status' => $response->status()]); + + return null; + } + + Cache::forever($this->getCacheKey('stars'), $stars); + + return $stars; + } + private function fetchLatestVersion(): ?Release { // Make a request to GitHub diff --git a/resources/views/components/navbar/device-dropdowns.blade.php b/resources/views/components/navbar/device-dropdowns.blade.php index 1d55475cb..89bf44b5b 100644 --- a/resources/views/components/navbar/device-dropdowns.blade.php +++ b/resources/views/components/navbar/device-dropdowns.blade.php @@ -1,5 +1,7 @@ @php $showShowcase = \App\Models\Showcase::approved()->count() >= 4; + $mobileAirStars = \App\Support\GitHub::mobileAir()->formattedStars(); + $desktopStars = \App\Support\GitHub::desktop()->formattedStars(); @endphp