11import { FastifyInstance } from 'fastify' ;
22import { supabaseAdmin } from '../server' ;
3+ import { Octokit } from 'octokit' ;
4+ import { env } from '../env' ;
5+ import { getCache , setCache } from '../utils/cache' ;
36
47export async function repoRoutes ( fastify : FastifyInstance ) {
58
@@ -18,23 +21,103 @@ export async function repoRoutes(fastify: FastifyInstance) {
1821 return { repos : data } ;
1922 } ) ;
2023
21- // GET /api/repos/:id — single repo detail
22- fastify . get < { Params : { id : string } } > ( '/repos/:id' , async ( request , reply ) => {
23- const { data, error } = await supabaseAdmin
24+ // GET /api/repos/:id — single repo detail with extended analytics
25+ fastify . get < { Params : { id : string } ; Querystring : { refresh ?: string } } > ( '/repos/:id' , async ( request , reply ) => {
26+ const { data : repo , error } = await supabaseAdmin
2427 . from ( 'repositories' )
2528 . select ( '*' )
2629 . eq ( 'id' , request . params . id )
2730 . eq ( 'owner_id' , request . userId )
2831 . single ( ) ;
2932
30- if ( error ) {
31- request . log . error ( { err : error , repoId : request . params . id } , 'Failed to fetch repository' ) ;
32- return reply . status ( 500 ) . send ( { error : 'Failed to fetch repository' } ) ;
33- }
34- if ( ! data ) {
33+ if ( error || ! repo ) {
34+ request . log . error ( { err : error , repoId : request . params . id } , 'Failed to fetch repository from DB' ) ;
3535 return reply . status ( 404 ) . send ( { error : 'Repository not found' } ) ;
3636 }
3737
38- return { repo : data } ;
38+ const refreshRequested = request . query . refresh === 'true' ;
39+ const cacheKey = `repo_details_${ repo . github_id } ` ;
40+
41+ // Check cache unless refresh is requested
42+ if ( ! refreshRequested ) {
43+ const cachedDetails = getCache < any > ( cacheKey ) ;
44+ if ( cachedDetails ) {
45+ request . log . info ( { repoId : repo . github_id } , 'Serving repo details from cache' ) ;
46+ return { repo : { ...repo , ...cachedDetails } } ;
47+ }
48+ } else {
49+ request . log . info ( { repoId : repo . github_id } , 'Force refresh requested, bypassing cache' ) ;
50+ }
51+
52+ // Parallel fetching from GitHub with detailed logging
53+ const octokit = new Octokit ( { auth : env . GITHUB_TOKEN } ) ;
54+
55+ // Safety check for name parsing
56+ const nameParts = repo . name . split ( '/' ) ;
57+ const owner = nameParts . length >= 2 ? nameParts [ 0 ] : '' ;
58+ const name = nameParts . length >= 2 ? nameParts [ 1 ] : nameParts [ 0 ] ;
59+
60+ request . log . info ( { owner, name, github_token_exists : ! ! env . GITHUB_TOKEN } , 'Starting GitHub API fetch' ) ;
61+
62+ if ( ! owner || ! name ) {
63+ request . log . error ( { fullName : repo . name } , 'Invalid repository full_name format' ) ;
64+ return { repo : { ...repo , languages : { } , contributors : [ ] , activity : [ ] , readme : '' } } ;
65+ }
66+
67+ try {
68+ const safeFetch = async < T , > ( promise : Promise < T > , label : string , fallback : T ) : Promise < T > => {
69+ try {
70+ const result = await promise ;
71+ request . log . info ( { label } , `Successfully fetched ${ label } ` ) ;
72+ return result ;
73+ } catch ( err : any ) {
74+ request . log . warn ( { err : err . message , status : err . status , label } , `Failed to fetch ${ label } ` ) ;
75+ return fallback ;
76+ }
77+ } ;
78+
79+ const [ languages , contributors , activity , readme ] = await Promise . all ( [
80+ safeFetch (
81+ octokit . rest . repos . listLanguages ( { owner, repo : name } ) . then ( r => r . data ) ,
82+ 'languages' ,
83+ { }
84+ ) ,
85+ safeFetch (
86+ octokit . rest . repos . listContributors ( { owner, repo : name , per_page : 20 } ) . then ( r => r . data . map ( c => ( {
87+ login : c . login ,
88+ avatar_url : c . avatar_url ,
89+ contributions : c . contributions ,
90+ html_url : c . html_url
91+ } ) ) ) ,
92+ 'contributors' ,
93+ [ ]
94+ ) ,
95+ safeFetch (
96+ octokit . rest . repos . getCommitActivityStats ( { owner, repo : name } ) . then ( r => r . data ) ,
97+ 'activity' ,
98+ [ ]
99+ ) ,
100+ safeFetch (
101+ octokit . rest . repos . getReadme ( { owner, repo : name } ) . then ( r => Buffer . from ( r . data . content , 'base64' ) . toString ( ) ) ,
102+ 'readme' ,
103+ ''
104+ )
105+ ] ) ;
106+
107+ const extendedDetails = {
108+ languages : languages || { } ,
109+ contributors : contributors || [ ] ,
110+ activity : Array . isArray ( activity ) ? activity . slice ( - 12 ) : [ ] ,
111+ readme : readme || ''
112+ } ;
113+
114+ // Cache the result
115+ setCache ( cacheKey , extendedDetails ) ;
116+
117+ return { repo : { ...repo , ...extendedDetails } } ;
118+ } catch ( err ) {
119+ request . log . error ( { err } , 'Unexpected crash in parallel fetching logic' ) ;
120+ return { repo : { ...repo , languages : { } , contributors : [ ] , activity : [ ] , readme : '' } } ;
121+ }
39122 } ) ;
40123}
0 commit comments