Skip to content

Commit 959dc1b

Browse files
authored
feat(docs): Use GitHub App over PAT and use octokit (#599)
* feat(docs): Use GitHub App over PAT and use octokit * Use typescript
1 parent fcbb276 commit 959dc1b

9 files changed

Lines changed: 540 additions & 185 deletions

File tree

.web/functions/api/extensions.js

Lines changed: 0 additions & 61 deletions
This file was deleted.

.web/functions/api/extensions.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/// <reference types="@cloudflare/workers-types" />
2+
3+
// functions/api/extensions.ts
4+
5+
import { getOctokit, type GitHubAppEnv } from './github-auth';
6+
7+
const CACHE_DURATION = 60 * 60; // Cache duration in seconds
8+
9+
interface ExtensionRepository {
10+
name: string;
11+
owner: string;
12+
description: string | null;
13+
stars: number;
14+
url: string;
15+
}
16+
17+
interface CloudflarePagesFunctionEnv extends GitHubAppEnv {
18+
GITHUB_CACHE: KVNamespace;
19+
}
20+
21+
interface CloudflarePagesFunctionContext {
22+
env: CloudflarePagesFunctionEnv;
23+
}
24+
25+
export async function onRequest(
26+
context: CloudflarePagesFunctionContext
27+
): Promise<Response> {
28+
const cacheKey = 'gate-extension-repositories';
29+
30+
// Access the KV namespace from context.env
31+
const GITHUB_CACHE = context.env.GITHUB_CACHE;
32+
33+
// Check for cached data
34+
const cachedResponse = await GITHUB_CACHE.get(cacheKey);
35+
if (cachedResponse) {
36+
return new Response(cachedResponse, {
37+
headers: {
38+
'Content-Type': 'application/json',
39+
'Access-Control-Allow-Origin': '*',
40+
},
41+
});
42+
}
43+
44+
try {
45+
// Get authenticated Octokit instance
46+
const octokit = await getOctokit(context.env, GITHUB_CACHE);
47+
48+
// Search for repositories with topic:gate-extension
49+
const { data } = await octokit.request('GET /search/repositories', {
50+
q: 'topic:gate-extension',
51+
sort: 'stars',
52+
order: 'desc',
53+
});
54+
55+
const libraries: ExtensionRepository[] = data.items.map((item) => ({
56+
name: item.name,
57+
owner: item.owner?.login ?? 'unknown',
58+
description: item.description,
59+
stars: item.stargazers_count,
60+
url: item.html_url,
61+
}));
62+
63+
// Cache the response
64+
await GITHUB_CACHE.put(cacheKey, JSON.stringify(libraries), {
65+
expirationTtl: CACHE_DURATION,
66+
});
67+
68+
return new Response(JSON.stringify(libraries), {
69+
headers: {
70+
'Content-Type': 'application/json',
71+
'Access-Control-Allow-Origin': '*',
72+
'Access-Control-Allow-Methods': 'GET',
73+
'Access-Control-Allow-Headers': 'Content-Type',
74+
},
75+
});
76+
} catch (error) {
77+
const errorMessage =
78+
error instanceof Error ? error.message : 'Unknown error';
79+
return new Response(`Error fetching data: ${errorMessage}`, {
80+
status: 500,
81+
});
82+
}
83+
}
84+

.web/functions/api/github-auth.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/// <reference types="@cloudflare/workers-types" />
2+
3+
// Shared utility for GitHub App authentication
4+
// This replaces PAT (Personal Access Token) authentication with GitHub App authentication
5+
// Uses JWT tokens for public repository searches (no installation required)
6+
7+
import { createAppAuth } from '@octokit/auth-app';
8+
import { Octokit } from '@octokit/core';
9+
10+
const JWT_TOKEN_CACHE_KEY = 'github-jwt-token';
11+
const JWT_TOKEN_CACHE_TTL = 8 * 60; // 8 minutes (JWT tokens expire after 10 minutes)
12+
13+
/**
14+
* Environment variables interface for GitHub App configuration
15+
*/
16+
export interface GitHubAppEnv {
17+
GITHUB_APP_ID: string;
18+
GITHUB_APP_PRIVATE_KEY: string;
19+
GITHUB_CACHE?: KVNamespace;
20+
}
21+
22+
/**
23+
* Get an authenticated Octokit instance using GitHub App JWT
24+
* @param env - Cloudflare environment variables
25+
* @param cache - Cache object (e.g., KV namespace)
26+
* @returns Authenticated Octokit instance
27+
*/
28+
export async function getOctokit(
29+
env: GitHubAppEnv,
30+
cache?: KVNamespace
31+
): Promise<Octokit> {
32+
// Get GitHub App credentials
33+
const appId = env.GITHUB_APP_ID;
34+
const privateKey = env.GITHUB_APP_PRIVATE_KEY;
35+
36+
if (!appId || !privateKey) {
37+
throw new Error(
38+
'GitHub App credentials not configured. Need GITHUB_APP_ID and GITHUB_APP_PRIVATE_KEY'
39+
);
40+
}
41+
42+
// Check cache first for JWT token
43+
let token: string | null = null;
44+
if (cache) {
45+
const cachedToken = await cache.get(JWT_TOKEN_CACHE_KEY);
46+
if (cachedToken) {
47+
token = cachedToken;
48+
}
49+
}
50+
51+
// Generate fresh JWT token if not cached
52+
if (!token) {
53+
// Create GitHub App auth instance
54+
const auth = createAppAuth({
55+
appId,
56+
privateKey,
57+
});
58+
59+
// Get JWT token (no installation needed for public repo searches)
60+
const authResult = await auth({
61+
type: 'app',
62+
});
63+
64+
token = authResult.token;
65+
66+
// Cache the token (JWT tokens are valid for 10 minutes)
67+
if (cache && token) {
68+
await cache.put(JWT_TOKEN_CACHE_KEY, token, {
69+
expirationTtl: JWT_TOKEN_CACHE_TTL,
70+
});
71+
}
72+
}
73+
74+
// Return authenticated Octokit instance
75+
return new Octokit({
76+
auth: token,
77+
});
78+
}
79+

.web/functions/api/go-modules.js

Lines changed: 0 additions & 99 deletions
This file was deleted.

0 commit comments

Comments
 (0)