(() =>
+ protected readonly sponsorsResource = httpResource<{ sponsors: GithubUser[] }>(() =>
this.isBrowser ? '/api/sponsors' : undefined,
);
@@ -48,6 +48,14 @@ export class Landing {
() => this.sponsorsResource.value()?.sponsors ?? [],
);
+ protected readonly contributorsResource = httpResource<{ contributors: GithubUser[] }>(() =>
+ this.isBrowser ? '/api/contributors' : undefined,
+ );
+
+ protected readonly contributors = computed(
+ () => this.contributorsResource.value()?.contributors ?? [],
+ );
+
protected readonly cards = [
{
icon: 'zap',
diff --git a/website/src/server/github-api.ts b/website/src/server/github-api.ts
index e9db180d5..e9aa8830e 100644
--- a/website/src/server/github-api.ts
+++ b/website/src/server/github-api.ts
@@ -371,6 +371,20 @@ githubApi.get('/leaderboard/:board', async (req, res) => {
res.json({ entries });
});
+/** All repository contributors for the landing page. */
+githubApi.get('/contributors', async (_req, res) => {
+ const { status, data } = await github(`/repos/${REPO}/contributors?per_page=100`, 3600);
+ if (status !== 200) {
+ res.status(503).json({ error: 'github request failed' });
+ return;
+ }
+ const contributors = (data as any[])
+ .filter((u) => !EXCLUDED_USERS.has(u.login) && u.type === 'User')
+ .map((u) => ({ login: u.login, avatar: u.avatar_url }));
+ res.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=86400');
+ res.json({ contributors });
+});
+
/** Repository stats for the landing page. */
githubApi.get('/stats', async (_req, res) => {
const { status, data } = await github(`/repos/${REPO}`, 900);