255 lines
7.4 KiB
TypeScript
255 lines
7.4 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { adminDb } from '@/lib/firebase/admin';
|
|
import { getApiUrl } from '@/lib/utils/api-url';
|
|
|
|
/**
|
|
* Complete Project Context API
|
|
* Returns everything an AI needs to understand the project state
|
|
*/
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ projectId: string }> }
|
|
) {
|
|
try {
|
|
const { projectId } = await params;
|
|
|
|
// 1. Load project metadata
|
|
const projectDoc = await adminDb
|
|
.collection('projects')
|
|
.doc(projectId)
|
|
.get();
|
|
|
|
if (!projectDoc.exists) {
|
|
return NextResponse.json(
|
|
{ error: 'Project not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const projectData = projectDoc.data();
|
|
|
|
// 2. Load timeline data
|
|
const timelineResponse = await fetch(
|
|
getApiUrl(`/api/projects/${projectId}/timeline`, request)
|
|
);
|
|
const timeline = timelineResponse.ok ? await timelineResponse.json() : null;
|
|
|
|
// 3. Load Git history summary
|
|
const gitResponse = await fetch(
|
|
getApiUrl(`/api/projects/${projectId}/git-history`, request)
|
|
);
|
|
const gitHistory = gitResponse.ok ? await gitResponse.json() : null;
|
|
|
|
// 4. Load extension activity
|
|
const activityResponse = await fetch(
|
|
getApiUrl(`/api/projects/${projectId}/activity`, request)
|
|
);
|
|
const activity = activityResponse.ok ? await activityResponse.json() : null;
|
|
|
|
// 5. Load uploaded documents
|
|
const documentsSnapshot = await adminDb
|
|
.collection('projects')
|
|
.doc(projectId)
|
|
.collection('documents')
|
|
.orderBy('uploadedAt', 'desc')
|
|
.get();
|
|
|
|
const documents = documentsSnapshot.docs.map(doc => ({
|
|
id: doc.id,
|
|
...doc.data()
|
|
}));
|
|
|
|
// 6. Get recent conversations (last 7 days)
|
|
const sevenDaysAgo = new Date();
|
|
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
|
|
const conversationsSnapshot = await adminDb
|
|
.collection('projects')
|
|
.doc(projectId)
|
|
.collection('cursorConversations')
|
|
.where('createdAt', '>=', sevenDaysAgo.toISOString())
|
|
.orderBy('createdAt', 'desc')
|
|
.limit(10)
|
|
.get();
|
|
|
|
const recentConversations = [];
|
|
for (const convDoc of conversationsSnapshot.docs) {
|
|
const conv = convDoc.data();
|
|
const messagesSnapshot = await adminDb
|
|
.collection('projects')
|
|
.doc(projectId)
|
|
.collection('cursorConversations')
|
|
.doc(convDoc.id)
|
|
.collection('messages')
|
|
.orderBy('createdAt', 'desc')
|
|
.limit(5)
|
|
.get();
|
|
|
|
recentConversations.push({
|
|
id: convDoc.id,
|
|
name: conv.name,
|
|
createdAt: conv.createdAt,
|
|
recentMessages: messagesSnapshot.docs.map(m => ({
|
|
type: m.data().type === 1 ? 'user' : 'assistant',
|
|
text: m.data().text?.substring(0, 200) + '...',
|
|
createdAt: m.data().createdAt
|
|
}))
|
|
});
|
|
}
|
|
|
|
// 7. Calculate key metrics
|
|
const activeDays = timeline?.days?.filter((d: any) =>
|
|
d.summary.totalGitCommits > 0 ||
|
|
d.summary.totalExtensionSessions > 0 ||
|
|
d.summary.totalCursorMessages > 0
|
|
).length || 0;
|
|
|
|
const topFiles = activity?.fileActivity?.slice(0, 10) || [];
|
|
|
|
// 8. Extract key milestones (commits with significant changes)
|
|
const keyMilestones = gitHistory?.commits
|
|
?.filter((c: any) => c.insertions + c.deletions > 1000)
|
|
.slice(0, 5)
|
|
.map((c: any) => ({
|
|
date: c.date,
|
|
message: c.message,
|
|
author: c.author,
|
|
impact: `+${c.insertions}/-${c.deletions} lines`
|
|
})) || [];
|
|
|
|
// 9. Generate AI-friendly summary
|
|
const context = {
|
|
project: {
|
|
id: projectId,
|
|
name: projectData?.name || 'Untitled Project',
|
|
vision: projectData?.vision || null,
|
|
description: projectData?.description || null,
|
|
createdAt: projectData?.createdAt || null,
|
|
githubRepo: projectData?.githubRepo || null
|
|
},
|
|
|
|
timeline: {
|
|
dateRange: {
|
|
earliest: timeline?.dateRange?.earliest,
|
|
latest: timeline?.dateRange?.latest,
|
|
totalDays: timeline?.dateRange?.totalDays || 0,
|
|
activeDays
|
|
},
|
|
dataSources: {
|
|
git: {
|
|
available: timeline?.dataSources?.git?.available || false,
|
|
totalCommits: timeline?.dataSources?.git?.totalRecords || 0,
|
|
dateRange: {
|
|
first: timeline?.dataSources?.git?.firstDate,
|
|
last: timeline?.dataSources?.git?.lastDate
|
|
}
|
|
},
|
|
extension: {
|
|
available: timeline?.dataSources?.extension?.available || false,
|
|
totalSessions: timeline?.dataSources?.extension?.totalRecords || 0,
|
|
dateRange: {
|
|
first: timeline?.dataSources?.extension?.firstDate,
|
|
last: timeline?.dataSources?.extension?.lastDate
|
|
}
|
|
},
|
|
cursor: {
|
|
available: timeline?.dataSources?.cursor?.available || false,
|
|
totalMessages: timeline?.dataSources?.cursor?.totalRecords || 0,
|
|
dateRange: {
|
|
first: timeline?.dataSources?.cursor?.firstDate,
|
|
last: timeline?.dataSources?.cursor?.lastDate
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
codebase: {
|
|
totalCommits: gitHistory?.totalCommits || 0,
|
|
totalLinesAdded: gitHistory?.totalInsertions || 0,
|
|
totalLinesRemoved: gitHistory?.totalDeletions || 0,
|
|
contributors: gitHistory?.authors || [],
|
|
topFiles: gitHistory?.topFiles?.slice(0, 20) || []
|
|
},
|
|
|
|
activity: {
|
|
totalSessions: activity?.totalSessions || 0,
|
|
uniqueFilesEdited: activity?.fileActivity?.length || 0,
|
|
topEditedFiles: topFiles,
|
|
recentConversations
|
|
},
|
|
|
|
milestones: keyMilestones,
|
|
|
|
documents: documents.map(doc => ({
|
|
id: doc.id,
|
|
title: doc.title,
|
|
type: doc.type,
|
|
uploadedAt: doc.uploadedAt,
|
|
contentPreview: doc.content?.substring(0, 500) + '...'
|
|
})),
|
|
|
|
summary: generateProjectSummary({
|
|
projectData,
|
|
timeline,
|
|
gitHistory,
|
|
activity,
|
|
documents
|
|
})
|
|
};
|
|
|
|
return NextResponse.json(context);
|
|
|
|
} catch (error) {
|
|
console.error('Error loading project context:', error);
|
|
return NextResponse.json(
|
|
{
|
|
error: 'Failed to load project context',
|
|
details: error instanceof Error ? error.message : String(error)
|
|
},
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Helper to generate human-readable summary
|
|
function generateProjectSummary(data: any): string {
|
|
const { projectData, timeline, gitHistory, activity, documents } = data;
|
|
|
|
const parts = [];
|
|
|
|
// Project basics
|
|
if (projectData?.name) {
|
|
parts.push(`Project: ${projectData.name}`);
|
|
}
|
|
|
|
if (projectData?.vision) {
|
|
parts.push(`Vision: ${projectData.vision}`);
|
|
}
|
|
|
|
// Timeline
|
|
if (timeline?.dateRange?.totalDays) {
|
|
parts.push(`Development span: ${timeline.dateRange.totalDays} days`);
|
|
}
|
|
|
|
// Git stats
|
|
if (gitHistory?.totalCommits) {
|
|
parts.push(
|
|
`Code: ${gitHistory.totalCommits} commits, ` +
|
|
`+${gitHistory.totalInsertions.toLocaleString()}/-${gitHistory.totalDeletions.toLocaleString()} lines`
|
|
);
|
|
}
|
|
|
|
// Activity
|
|
if (activity?.totalSessions) {
|
|
parts.push(`Activity: ${activity.totalSessions} development sessions`);
|
|
}
|
|
|
|
// Documents
|
|
if (documents?.length) {
|
|
parts.push(`Documentation: ${documents.length} documents uploaded`);
|
|
}
|
|
|
|
return parts.join(' | ');
|
|
}
|
|
|