import { NextResponse } from 'next/server'; import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin'; /** * Fetch repository file tree from GitHub * GET /api/github/repo-tree?owner=X&repo=Y */ export async function GET(request: Request) { try { const authHeader = request.headers.get('Authorization'); if (!authHeader?.startsWith('Bearer ')) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const idToken = authHeader.split('Bearer ')[1]; const adminAuth = getAdminAuth(); const adminDb = getAdminDb(); let userId: string; try { const decodedToken = await adminAuth.verifyIdToken(idToken); userId = decodedToken.uid; } catch (error) { return NextResponse.json({ error: 'Invalid token' }, { status: 401 }); } const url = new URL(request.url); const owner = url.searchParams.get('owner'); const repo = url.searchParams.get('repo'); const branch = url.searchParams.get('branch') || 'main'; if (!owner || !repo) { return NextResponse.json({ error: 'Missing owner or repo' }, { status: 400 }); } // Get GitHub connection const connectionDoc = await adminDb .collection('githubConnections') .doc(userId) .get(); if (!connectionDoc.exists) { return NextResponse.json( { error: 'GitHub not connected' }, { status: 404 } ); } const connection = connectionDoc.data()!; const accessToken = connection.accessToken; // TODO: Decrypt // Fetch repository tree from GitHub API (recursive) const response = await fetch( `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}?recursive=1`, { headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/vnd.github.v3+json', }, } ); if (!response.ok) { const error = await response.json(); throw new Error(`GitHub API error: ${error.message || response.statusText}`); } const data = await response.json(); // Filter to only include files (not directories) // and exclude common non-code files const excludePatterns = [ /node_modules\//, /\.git\//, /dist\//, /build\//, /\.next\//, /coverage\//, /\.cache\//, /\.env/, /package-lock\.json$/, /yarn\.lock$/, /pnpm-lock\.yaml$/, /\.png$/, /\.jpg$/, /\.jpeg$/, /\.gif$/, /\.svg$/, /\.ico$/, /\.woff$/, /\.woff2$/, /\.ttf$/, /\.eot$/, /\.min\.js$/, /\.min\.css$/, /\.map$/, ]; // Include common code file extensions const includePatterns = [ /\.(ts|tsx|js|jsx|py|java|go|rs|cpp|c|h|cs|rb|php|swift|kt)$/, /\.(json|yaml|yml|toml|xml)$/, /\.(md|txt)$/, /\.(sql|graphql|proto)$/, /\.(css|scss|sass|less)$/, /\.(html|htm)$/, /Dockerfile$/, /Makefile$/, /README$/, ]; const files = data.tree .filter((item: any) => item.type === 'blob') .filter((item: any) => { // Exclude patterns if (excludePatterns.some(pattern => pattern.test(item.path))) { return false; } // Include patterns return includePatterns.some(pattern => pattern.test(item.path)); }) .map((item: any) => ({ path: item.path, sha: item.sha, size: item.size, url: item.url, })); console.log(`[GitHub Tree] Found ${files.length} code files in ${owner}/${repo}`); return NextResponse.json({ owner, repo, branch, totalFiles: files.length, files, }); } catch (error) { console.error('[GitHub Tree] Error:', error); return NextResponse.json( { error: 'Failed to fetch repository tree', details: error instanceof Error ? error.message : String(error), }, { status: 500 } ); } }