Skip to content

Commit 5004116

Browse files
committed
chore: paginate graphql call
1 parent 1e8088e commit 5004116

File tree

1 file changed

+76
-70
lines changed

1 file changed

+76
-70
lines changed

server/api/contributors.get.ts

Lines changed: 76 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -143,87 +143,93 @@ async function fetchGitHubUserData(
143143
): Promise<Set<string>> {
144144
if (logins.length === 0) return new Set()
145145

146-
// Build aliased GraphQL query: user0: user(login: "x") { hasSponsorsListing login }
147-
const fragments = logins.map(
148-
(login, i) =>
149-
`user${i}: user(login: "${login}") { hasSponsorsListing login name bio company companyHTML location websiteUrl twitterUsername socialAccounts(first: 10) { nodes { provider url } } }`,
150-
)
151-
const query = `{ ${fragments.join('\n')} }`
152-
153-
try {
154-
const response = await fetch('https://api.github.com/graphql', {
155-
method: 'POST',
156-
headers: {
157-
'Authorization': `Bearer ${token}`,
158-
'Content-Type': 'application/json',
159-
'User-Agent': 'npmx',
160-
},
161-
body: JSON.stringify({ query }),
162-
})
146+
const sponsorable = new Set<string>()
147+
const chunkSize = 100
148+
149+
for (let i = 0; i < logins.length; i += chunkSize) {
150+
const chunk = logins.slice(i, i + chunkSize)
151+
152+
// Build aliased GraphQL query: user0: user(login: "x") { hasSponsorsListing login }
153+
const fragments = chunk.map(
154+
(login, idx) =>
155+
`user${idx}: user(login: "${login}") { hasSponsorsListing login name bio company companyHTML location websiteUrl twitterUsername socialAccounts(first: 10) { nodes { provider url } } }`,
156+
)
157+
const query = `{ ${fragments.join('\n')} }`
158+
159+
try {
160+
const response = await fetch('https://api.github.com/graphql', {
161+
method: 'POST',
162+
headers: {
163+
'Authorization': `Bearer ${token}`,
164+
'Content-Type': 'application/json',
165+
'User-Agent': 'npmx',
166+
},
167+
body: JSON.stringify({ query }),
168+
})
163169

164-
if (!response.ok) {
165-
console.warn(`Failed to fetch sponsors info: ${response.status}`)
166-
return new Set()
167-
}
170+
if (!response.ok) {
171+
console.warn(`Failed to fetch sponsors info (chunk ${i}): ${response.status}`)
172+
continue
173+
}
168174

169-
const json = (await response.json()) as {
170-
data?: Record<
171-
string,
172-
| (GitHubUserData & {
173-
login: string
174-
hasSponsorsListing: boolean
175-
socialAccounts: { nodes: { provider: string; url: string }[] }
176-
})
177-
| null
178-
>
179-
}
175+
const json = (await response.json()) as {
176+
data?: Record<
177+
string,
178+
| (GitHubUserData & {
179+
login: string
180+
hasSponsorsListing: boolean
181+
socialAccounts: { nodes: { provider: string; url: string }[] }
182+
})
183+
| null
184+
>
185+
}
180186

181-
const sponsorable = new Set<string>()
182-
if (json.data) {
183-
for (const user of Object.values(json.data)) {
184-
if (!user) continue
185-
if (user.hasSponsorsListing) {
186-
sponsorable.add(user.login)
187-
}
187+
if (json.data) {
188+
for (const user of Object.values(json.data)) {
189+
if (!user) continue
190+
if (user.hasSponsorsListing) {
191+
sponsorable.add(user.login)
192+
}
188193

189-
// Extract Bluesky and Mastodon from socialAccounts
190-
let blueskyHandle: string | null = null
191-
let mastodonUrl: string | null = null
192-
193-
if (user.socialAccounts?.nodes) {
194-
for (const account of user.socialAccounts.nodes) {
195-
if (account.url.includes('bsky.app')) {
196-
// Extract handle from URL: https://bsky.app/profile/handle.bsky.social
197-
const match = account.url.match(/bsky\.app\/profile\/([^/?]+)/)
198-
if (match) {
199-
blueskyHandle = match[1] as string
194+
// Extract Bluesky and Mastodon from socialAccounts
195+
let blueskyHandle: string | null = null
196+
let mastodonUrl: string | null = null
197+
198+
if (user.socialAccounts?.nodes) {
199+
for (const account of user.socialAccounts.nodes) {
200+
if (account.url.includes('bsky.app')) {
201+
// Extract handle from URL: https://bsky.app/profile/handle.bsky.social
202+
const match = account.url.match(/bsky\.app\/profile\/([^/?]+)/)
203+
if (match) {
204+
blueskyHandle = match[1] as string
205+
}
206+
} else if (account.provider === 'MASTODON') {
207+
mastodonUrl = cleanString(account.url, true)
200208
}
201-
} else if (account.provider === 'MASTODON') {
202-
mastodonUrl = cleanString(account.url, true)
203209
}
204210
}
205-
}
206211

207-
// --- SERVER-SIDE SANITIZATION AND BATCHING ---
208-
usersData.set(user.login, {
209-
name: cleanString(user.name),
210-
bio: cleanString(user.bio),
211-
company: cleanString(user.company),
212-
// Rich HTML sanitization for company mentions/orgs
213-
companyHTML: sanitizeGitHubHTML(user.companyHTML),
214-
location: cleanString(user.location),
215-
websiteUrl: cleanString(user.websiteUrl, true),
216-
twitterUsername: cleanString(user.twitterUsername),
217-
blueskyHandle,
218-
mastodonUrl,
219-
})
212+
// --- SERVER-SIDE SANITIZATION AND BATCHING ---
213+
usersData.set(user.login, {
214+
name: cleanString(user.name),
215+
bio: cleanString(user.bio),
216+
company: cleanString(user.company),
217+
// Rich HTML sanitization for company mentions/orgs
218+
companyHTML: sanitizeGitHubHTML(user.companyHTML),
219+
location: cleanString(user.location),
220+
websiteUrl: cleanString(user.websiteUrl, true),
221+
twitterUsername: cleanString(user.twitterUsername),
222+
blueskyHandle,
223+
mastodonUrl,
224+
})
225+
}
220226
}
227+
} catch (error) {
228+
console.warn(`Failed to fetch sponsors info (chunk ${i}):`, error)
221229
}
222-
return sponsorable
223-
} catch (error) {
224-
console.warn('Failed to fetch sponsors info:', error)
225-
return new Set()
226230
}
231+
232+
return sponsorable
227233
}
228234

229235
function getRoleInfo(login: string, teams: TeamMembers): { role: Role; order: number } {

0 commit comments

Comments
 (0)