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-dropdown-item.blade.php b/resources/views/components/navbar/device-dropdown-item.blade.php index d1851ca70..ec8cdde84 100644 --- a/resources/views/components/navbar/device-dropdown-item.blade.php +++ b/resources/views/components/navbar/device-dropdown-item.blade.php @@ -1,6 +1,7 @@ @props([ 'href' => '#', 'title' => '', + 'titlePrefix' => null, 'subtitle' => null, 'icon' => null, 'github' => null, @@ -10,7 +11,7 @@ @if ($title) -
{{ $title }}
+
@if ($titlePrefix){{ $titlePrefix }}@endif{{ $title }}
@endif @if ($subtitle) diff --git a/resources/views/components/navbar/device-dropdowns.blade.php b/resources/views/components/navbar/device-dropdowns.blade.php index 1d55475cb..d7a4f2a68 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