@@ -163,7 +163,7 @@ export function CommunityStatsProvider({
163163 const {
164164 siteConfig : { customFields } ,
165165 } = useDocusaurusContext ( ) ;
166- const token = customFields ?. gitToken || "" ;
166+ const backendApiUrl = ( customFields ?. backendApiUrl as string ) || "http://localhost:5000 " ;
167167
168168 const [ loading , setLoading ] = useState ( false ) ; // Start with false to avoid hourglass
169169 const [ error , setError ] = useState < string | null > ( null ) ;
@@ -251,164 +251,6 @@ export function CommunityStatsProvider({
251251 setCurrentTimeFilter ( filter ) ;
252252 } , [ ] ) ;
253253
254- const fetchAllOrgRepos = useCallback (
255- async ( headers : Record < string , string > ) => {
256- const repos : any [ ] = [ ] ;
257- let page = 1 ;
258- while ( true ) {
259- const resp = await fetch (
260- `https://api.github.com/orgs/${ GITHUB_ORG } /repos?type=public&per_page=100&page=${ page } ` ,
261- {
262- headers,
263- } ,
264- ) ;
265- if ( ! resp . ok ) {
266- throw new Error (
267- `Failed to fetch org repos: ${ resp . status } ${ resp . statusText } ` ,
268- ) ;
269- }
270- const data = await resp . json ( ) ;
271- repos . push ( ...data ) ;
272- if ( ! Array . isArray ( data ) || data . length < 100 ) break ;
273- page ++ ;
274- }
275- return repos ;
276- } ,
277- [ ] ,
278- ) ;
279-
280- const fetchMergedPRsForRepo = useCallback (
281- async ( repoName : string , headers : Record < string , string > ) => {
282- const mergedPRs : PullRequestItem [ ] = [ ] ;
283-
284- // First, get the first page to estimate total pages
285- const firstResp = await fetch (
286- `https://api.github.com/repos/${ GITHUB_ORG } /${ repoName } /pulls?state=closed&per_page=100&page=1` ,
287- { headers } ,
288- ) ;
289-
290- if ( ! firstResp . ok ) {
291- console . warn (
292- `Failed to fetch PRs for ${ repoName } : ${ firstResp . status } ${ firstResp . statusText } ` ,
293- ) ;
294- return [ ] ;
295- }
296-
297- const firstPRs : PullRequestItem [ ] = await firstResp . json ( ) ;
298- if ( ! Array . isArray ( firstPRs ) || firstPRs . length === 0 ) return [ ] ;
299-
300- const firstPageMerged = firstPRs . filter ( ( pr ) => Boolean ( pr . merged_at ) ) ;
301- mergedPRs . push ( ...firstPageMerged ) ;
302-
303- // If we got less than 100, that's all there is
304- if ( firstPRs . length < 100 ) return mergedPRs ;
305-
306- // Create parallel requests for remaining pages
307- const pagePromises : Promise < PullRequestItem [ ] > [ ] = [ ] ;
308- const maxPages = Math . min ( MAX_PAGES_PER_REPO , 10 ) ;
309-
310- for ( let i = 2 ; i <= maxPages ; i ++ ) {
311- pagePromises . push (
312- fetch (
313- `https://api.github.com/repos/${ GITHUB_ORG } /${ repoName } /pulls?state=closed&per_page=100&page=${ i } ` ,
314- { headers } ,
315- )
316- . then ( async ( resp ) => {
317- if ( ! resp . ok ) return [ ] ;
318- const prs : PullRequestItem [ ] = await resp . json ( ) ;
319- if ( ! Array . isArray ( prs ) ) return [ ] ;
320- return prs . filter ( ( pr ) => Boolean ( pr . merged_at ) ) ;
321- } )
322- . catch ( ( ) => [ ] ) ,
323- ) ;
324- }
325-
326- // Wait for all pages in parallel
327- const remainingPages = await Promise . all ( pagePromises ) ;
328- remainingPages . forEach ( ( pagePRs ) => {
329- if ( pagePRs . length > 0 ) mergedPRs . push ( ...pagePRs ) ;
330- } ) ;
331-
332- return mergedPRs ;
333- } ,
334- [ ] ,
335- ) ;
336-
337- // Enhanced processing function that stores only valid PRs with points
338- const processBatch = useCallback (
339- async (
340- repos : any [ ] ,
341- headers : Record < string , string > ,
342- ) : Promise < {
343- contributorMap : Map < string , FullContributor > ;
344- totalMergedPRs : number ;
345- } > => {
346- const contributorMap = new Map < string , FullContributor > ( ) ;
347- let totalMergedPRs = 0 ;
348-
349- // Process repos in batches to control concurrency
350- for ( let i = 0 ; i < repos . length ; i += MAX_CONCURRENT_REQUESTS ) {
351- const batch = repos . slice ( i , i + MAX_CONCURRENT_REQUESTS ) ;
352-
353- const promises = batch . map ( async ( repo ) => {
354- if ( repo . archived ) return { mergedPRs : [ ] , repoName : repo . name } ;
355-
356- try {
357- const mergedPRs = await fetchMergedPRsForRepo ( repo . name , headers ) ;
358- return { mergedPRs, repoName : repo . name } ;
359- } catch ( error ) {
360- console . warn ( `Skipping repo ${ repo . name } due to error:` , error ) ;
361- return { mergedPRs : [ ] , repoName : repo . name } ;
362- }
363- } ) ;
364-
365- // Wait for current batch to complete
366- const results = await Promise . all ( promises ) ;
367-
368- // Process results from this batch
369- results . forEach ( ( { mergedPRs, repoName } ) => {
370- mergedPRs . forEach ( ( pr ) => {
371- // Calculate points for this PR based on labels
372- const prPoints = calculatePointsForPR ( pr . labels ) ;
373-
374- // ONLY store PRs that have points (i.e., have "recode" label and a level label)
375- if ( prPoints > 0 ) {
376- totalMergedPRs ++ ;
377-
378- const username = pr . user . login ;
379- if ( ! contributorMap . has ( username ) ) {
380- contributorMap . set ( username , {
381- username,
382- avatar : pr . user . avatar_url ,
383- profile : pr . user . html_url ,
384- points : 0 , // Will be calculated later based on filter
385- prs : 0 , // Will be calculated later based on filter
386- allPRDetails : [ ] , // Store only valid PRs here
387- } ) ;
388- }
389- const contributor = contributorMap . get ( username ) ! ;
390-
391- // Add detailed PR information only if it has all required fields
392- if ( pr . title && pr . html_url && pr . merged_at && pr . number ) {
393- contributor . allPRDetails . push ( {
394- title : pr . title ,
395- url : pr . html_url ,
396- mergedAt : pr . merged_at ,
397- repoName,
398- number : pr . number ,
399- points : prPoints ,
400- } ) ;
401- }
402- }
403- } ) ;
404- } ) ;
405- }
406-
407- return { contributorMap, totalMergedPRs } ;
408- } ,
409- [ fetchMergedPRsForRepo ] ,
410- ) ;
411-
412254 const fetchAllStats = useCallback (
413255 async ( signal : AbortSignal ) => {
414256 // Check cache first and load it immediately without showing loading state
@@ -433,57 +275,52 @@ export function CommunityStatsProvider({
433275
434276 setError ( null ) ;
435277
436- if ( ! token ) {
437- setError (
438- "GitHub token not found. Please set customFields.gitToken in docusaurus.config.js." ,
439- ) ;
440- setLoading ( false ) ;
441- return ;
442- }
443-
444278 try {
445- const headers : Record < string , string > = {
446- Authorization : `token ${ token } ` ,
447- Accept : "application/vnd.github.v3+json" ,
448- } ;
449-
450- // Fetch both org stats and repos in parallel
451- const [ orgStats , repos ] = await Promise . all ( [
452- githubService . fetchOrganizationStats ( signal ) ,
453- fetchAllOrgRepos ( headers ) ,
279+ const [ leaderboardResp , statsResp ] = await Promise . all ( [
280+ fetch ( `${ backendApiUrl } /api/leaderboard` , { signal } ) ,
281+ fetch ( `${ backendApiUrl } /api/stats` , { signal } )
454282 ] ) ;
455283
456- // Set org stats immediately
457- setGithubStarCount ( orgStats . totalStars ) ;
458- setGithubContributorsCount ( orgStats . totalContributors ) ;
459- setGithubForksCount ( orgStats . totalForks ) ;
460- setGithubReposCount ( orgStats . publicRepositories ) ;
461- setGithubDiscussionsCount ( orgStats . discussionsCount ) ;
462- setLastUpdated ( new Date ( orgStats . lastUpdated ) ) ;
463-
464- // Process leaderboard data with concurrent processing
465- const { contributorMap, totalMergedPRs } = await processBatch (
466- repos ,
467- headers ,
468- ) ;
284+ if ( ! leaderboardResp . ok || ! statsResp . ok ) {
285+ throw new Error ( "Failed to fetch leaderboard data from backend server" ) ;
286+ }
469287
470- const contributorsArray = Array . from ( contributorMap . values ( ) ) ;
288+ const leaderboardData = await leaderboardResp . json ( ) ;
289+ const statsData = await statsResp . json ( ) ;
290+
291+ // Set org stats immediately
292+ setGithubStarCount ( statsData . totalStars ) ;
293+ setGithubContributorsCount ( statsData . totalContributors ) ;
294+ setGithubForksCount ( statsData . totalForks ) ;
295+ setGithubReposCount ( statsData . publicRepositories ) ;
296+ setGithubDiscussionsCount ( statsData . discussionsCount ) ;
297+ setLastUpdated ( new Date ( statsData . lastUpdated ) ) ;
298+
299+ // Format to FullContributor (which matches contributor mapping)
300+ const contributorsArray : FullContributor [ ] = ( leaderboardData . contributors || [ ] ) . map ( ( c : any ) => ( {
301+ username : c . username ,
302+ avatar : c . avatar ,
303+ profile : c . profile ,
304+ points : c . points ,
305+ prs : c . prs ,
306+ allPRDetails : c . prDetails || [ ] // Backend stores complete list
307+ } ) ) ;
471308
472309 setAllContributors ( contributorsArray ) ;
473310
474311 // Cache the results (raw data without filtering)
475312 setCache ( {
476313 data : {
477314 contributors : contributorsArray ,
478- rawStats : { totalPRs : totalMergedPRs } ,
315+ rawStats : { totalPRs : statsData . totalContributors } ,
479316 } ,
480317 timestamp : now ,
481318 } ) ;
482319 } catch ( err : any ) {
483320 if ( err . name !== "AbortError" ) {
484- console . error ( "Error fetching GitHub organization stats :" , err ) ;
321+ console . error ( "Error fetching stats from backend :" , err ) ;
485322 setError (
486- err instanceof Error ? err . message : "Failed to fetch GitHub stats" ,
323+ err instanceof Error ? err . message : "Failed to fetch stats from backend " ,
487324 ) ;
488325
489326 // Set fallback values on error
@@ -497,7 +334,7 @@ export function CommunityStatsProvider({
497334 setLoading ( false ) ;
498335 }
499336 } ,
500- [ token , fetchAllOrgRepos , processBatch , cache ] ,
337+ [ backendApiUrl , cache ] ,
501338 ) ;
502339
503340 const clearCache = useCallback ( ( ) => {
0 commit comments