Files
vibn-frontend/app/api/github/repo-tree/route.ts

150 lines
3.9 KiB
TypeScript

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 }
);
}
}