From 8b8c6b9309f21ab881f7710f0dd54e7469d7f0c2 Mon Sep 17 00:00:00 2001 From: richardkeep Date: Tue, 20 Oct 2026 17:22:56 +0300 Subject: [PATCH 1/2] Load About page contributors dynamically from GitHub --- app/Http/Controllers/AboutController.php | 5 +- app/Queries/RepositoryContributorsQuery.php | 51 +++++++++++++++++++ resources/js/lib/about.ts | 50 ------------------ resources/js/pages/about/show.tsx | 22 ++++++-- .../Controllers/AboutControllerTest.php | 14 +++++ .../RepositoryContributorsQueryTest.php | 45 ++++++++++++++++ 6 files changed, 130 insertions(+), 57 deletions(-) create mode 100644 app/Queries/RepositoryContributorsQuery.php create mode 100644 tests/Feature/RepositoryContributorsQueryTest.php diff --git a/app/Http/Controllers/AboutController.php b/app/Http/Controllers/AboutController.php index 98b461175..083cc3333 100644 --- a/app/Http/Controllers/AboutController.php +++ b/app/Http/Controllers/AboutController.php @@ -4,15 +4,16 @@ namespace App\Http\Controllers; +use App\Queries\RepositoryContributorsQuery; use App\Services\PageMetadata; use Inertia\Inertia; use Inertia\Response; final readonly class AboutController { - public function show(PageMetadata $metadata): Response + public function show(PageMetadata $metadata, RepositoryContributorsQuery $contributors): Response { - return Inertia::render('about/show')->withViewData(['preview' => $metadata->page( + return Inertia::render('about/show', ['contributors' => $contributors->get(...)])->withViewData(['preview' => $metadata->page( 'About - /dev/null', 'How /dev/null was built in the open: from the first commit on stream to the developers who joined and the people who shipped it.', route('about.show'), diff --git a/app/Queries/RepositoryContributorsQuery.php b/app/Queries/RepositoryContributorsQuery.php new file mode 100644 index 000000000..909ab990f --- /dev/null +++ b/app/Queries/RepositoryContributorsQuery.php @@ -0,0 +1,51 @@ + + */ + public function get(): Collection + { + $repository = config()->string('services.github.repository'); + + return Cache::remember("repository-contributors:{$repository}", 3600, function () use ($repository): Collection { + $contributors = collect(); + try { + for ($page = 1; ; $page++) { + $response = $this->github->request()->get("repos/{$repository}/contributors", ['per_page' => 100, 'page' => $page])->throw(); + $accounts = $response->collect(); + $contributors = $contributors->concat($accounts->where('type', 'User')->mapInto(Fluent::class)->map(fn (Fluent $account): array => [ + 'login' => $account->string('login')->value(), + 'commits' => $account->integer('contributions'), + ])); + + if ($accounts->count() < 100) { + break; + } + } + } catch (HttpClientException) { + return collect(); + } + + return $contributors->sortByDesc('commits')->values(); + }); + } +} diff --git a/resources/js/lib/about.ts b/resources/js/lib/about.ts index c6bbc3621..faaeb6e56 100644 --- a/resources/js/lib/about.ts +++ b/resources/js/lib/about.ts @@ -181,53 +181,3 @@ export const storyAfter: StoryEvent[] = [ }, }, ]; - -/** - * Everyone with a commit on production, most commits first. Bots and agents are left out. - */ -export const storyContributors: StoryContributor[] = [ - { login: 'xHeaven', commits: 199 }, - { login: 'nunomaduro', commits: 176 }, - { login: 'mnapoli', commits: 61 }, - { login: 'brendt', commits: 42 }, - { login: 'simonhamp', commits: 18 }, - { login: 'paulinevos', commits: 16 }, - { login: 'jubianchi', commits: 16 }, - { login: 'MrPunyapal', commits: 15 }, - { login: 'sadiqk2', commits: 8 }, - { login: 'camilleislasse', commits: 6 }, - { login: 'hbenabdallah', commits: 5 }, - { login: 'vtlxx', commits: 5 }, - { login: 'robelsust', commits: 4 }, - { login: 'HelgeSverre', commits: 4 }, - { login: 'SanderMuller', commits: 3 }, - { login: 'MaximeGoncalves', commits: 3 }, - { login: 'mathiasonea', commits: 3 }, - { login: 'fernandofreamunde', commits: 3 }, - { login: 'mon4ssi', commits: 2 }, - { login: 'hbenjacquet', commits: 2 }, - { login: 'Akiyo974', commits: 2 }, - { login: 'Chemaclass', commits: 2 }, - { login: 'eznix86', commits: 2 }, - { login: 'BENNIEV-ZA', commits: 2 }, - { login: 'ahosker', commits: 2 }, - { login: 'eramitgupta', commits: 1 }, - { login: 'utsavsomaiya', commits: 1 }, - { login: 'tooinfinity', commits: 1 }, - { login: 'murrant', commits: 1 }, - { login: 'RobinMalfait', commits: 1 }, - { login: 'prateekbhujel', commits: 1 }, - { login: 'ondrejmirtes', commits: 1 }, - { login: 'lazarospsa', commits: 1 }, - { login: 'kachelle', commits: 1 }, - { login: 'kadirtikil', commits: 1 }, - { login: 'jjaybrown', commits: 1 }, - { login: 'GTS-MEGHNI', commits: 1 }, - { login: 'faissaloux', commits: 1 }, - { login: 'fabpl', commits: 1 }, - { login: 'danielh-official', commits: 1 }, - { login: 'chrisgocode', commits: 1 }, - { login: 'AmanuelLegese', commits: 1 }, - { login: 'alaminfirdows', commits: 1 }, - { login: 'aaqidmasoodi', commits: 1 }, -]; diff --git a/resources/js/pages/about/show.tsx b/resources/js/pages/about/show.tsx index 409804079..5d38a2949 100644 --- a/resources/js/pages/about/show.tsx +++ b/resources/js/pages/about/show.tsx @@ -16,6 +16,7 @@ import { import AppLayout from '@/layouts/app-layout'; import type { StoryCommit, + StoryContributor, StoryEvent, StoryEventKind, StoryStream, @@ -24,7 +25,6 @@ import { storyAfter, storyAfterLane, storyBefore, - storyContributors, storyFounders, storyStreams, } from '@/lib/about'; @@ -264,10 +264,18 @@ function Stream({ stream }: { stream: StoryStream }) { ); } -function Contributors() { +function Contributors({ contributors }: { contributors: StoryContributor[] }) { + if (contributors.length === 0) { + return ( +

+ The contributor list is temporarily unavailable. Please check + back later. +

+ ); + } return (