VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
import { NextResponse } from 'next/server';
import { getAdminDb } from '@/lib/firebase/admin';
import { FieldValue } from 'firebase-admin/firestore';
export async function POST(request: Request) {
try {
const body = await request.json().catch(() => ({}));
const projectId = (body.projectId ?? '').trim();
if (!projectId) {
return NextResponse.json(
{ error: 'projectId is required' },
{ status: 400 },
);
}
const adminDb = getAdminDb();
const docRef = adminDb.collection('chat_conversations').doc(projectId);
await adminDb.runTransaction(async (tx) => {
const snapshot = await tx.get(docRef);
const existing = (snapshot.exists ? (snapshot.data()?.messages as unknown[]) : []) ?? [];
const now = new Date().toISOString();
const newMessages = [
{
role: 'user' as const,
content: '[debug] test user message',
createdAt: now,
},
{
role: 'assistant' as const,
content: '[debug] test assistant reply',
createdAt: now,
},
];
tx.set(
docRef,
{
projectId,
messages: [...existing, ...newMessages],
updatedAt: FieldValue.serverTimestamp(),
},
{ merge: true },
);
});
return NextResponse.json({ success: true });
} catch (error) {
console.error('[debug/append-conversation] Failed to append messages', error);
return NextResponse.json(
{
error: 'Failed to append debug conversation messages',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,88 @@
/**
* Debug API to check session links
*/
import { NextResponse } from 'next/server';
import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin';
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 });
}
// Get all user's sessions
const sessionsSnapshot = await adminDb
.collection('sessions')
.where('userId', '==', userId)
.get();
const linked: any[] = [];
const unlinked: any[] = [];
sessionsSnapshot.docs.forEach(doc => {
const data = doc.data();
const sessionInfo = {
id: doc.id,
workspaceName: data.workspaceName || 'Unknown',
workspacePath: data.workspacePath,
projectId: data.projectId,
needsProjectAssociation: data.needsProjectAssociation,
createdAt: data.createdAt?.toDate?.() || data.createdAt,
};
if (data.projectId) {
linked.push(sessionInfo);
} else {
unlinked.push(sessionInfo);
}
});
// Get all user's projects
const projectsSnapshot = await adminDb
.collection('projects')
.where('userId', '==', userId)
.get();
const projects = projectsSnapshot.docs.map(doc => ({
id: doc.id,
name: doc.data().productName || doc.data().name,
workspacePath: doc.data().workspacePath,
}));
return NextResponse.json({
summary: {
totalSessions: sessionsSnapshot.size,
linkedSessions: linked.length,
unlinkedSessions: unlinked.length,
totalProjects: projects.length,
},
linked,
unlinked,
projects,
});
} catch (error) {
console.error('Debug check error:', error);
return NextResponse.json(
{
error: 'Failed to check links',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,62 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json(
{ error: 'Missing projectId parameter' },
{ status: 400 }
);
}
// Check if project exists
const projectDoc = await adminDb.collection('projects').doc(projectId).get();
if (!projectDoc.exists) {
// List all projects to help debug
const allProjectsSnapshot = await adminDb.collection('projects').limit(20).get();
const allProjects = allProjectsSnapshot.docs.map(doc => ({
id: doc.id,
name: doc.data().name,
userId: doc.data().userId,
createdAt: doc.data().createdAt
}));
return NextResponse.json({
exists: false,
projectId,
message: 'Project not found',
availableProjects: allProjects
});
}
const projectData = projectDoc.data();
return NextResponse.json({
exists: true,
projectId,
project: {
name: projectData?.name,
userId: projectData?.userId,
createdAt: projectData?.createdAt,
githubRepo: projectData?.githubRepo
}
});
} catch (error) {
console.error('Error checking project:', error);
return NextResponse.json(
{ error: 'Failed to check project', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,44 @@
import { NextResponse } from 'next/server';
import { getAdminDb } from '@/lib/firebase/admin';
export async function GET(request: Request) {
try {
const url = new URL(request.url);
const projectId = url.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
const adminDb = getAdminDb();
// Get contextSources subcollection
const contextSourcesRef = adminDb
.collection('projects')
.doc(projectId)
.collection('contextSources');
const snapshot = await contextSourcesRef.get();
const sources = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}));
return NextResponse.json({
projectId,
count: sources.length,
sources,
});
} catch (error) {
console.error('[debug/context-sources] Error:', error);
return NextResponse.json(
{
error: 'Failed to fetch context sources',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,72 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get workspace metadata
const workspaceMetaDoc = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorData')
.doc('workspace-meta')
.get();
const workspaceMeta = workspaceMetaDoc.exists ? workspaceMetaDoc.data() : null;
// Get all conversations
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.get();
// Analyze by score
const negative = conversationsSnapshot.docs
.map(doc => ({ name: doc.data().name, score: doc.data().relevanceScore || 0 }))
.filter(c => c.score < 0)
.sort((a, b) => a.score - b.score);
const positive = conversationsSnapshot.docs
.map(doc => ({ name: doc.data().name, score: doc.data().relevanceScore || 0 }))
.filter(c => c.score > 0)
.sort((a, b) => b.score - a.score);
const neutral = conversationsSnapshot.docs
.filter(doc => (doc.data().relevanceScore || 0) === 0)
.length;
return NextResponse.json({
workspacePath: workspaceMeta?.workspacePath,
githubUrl: workspaceMeta?.githubUrl,
workspaceFilesCount: workspaceMeta?.workspaceFiles?.length || 0,
workspaceFilesSample: (workspaceMeta?.workspaceFiles || []).slice(0, 20),
totalGenerations: workspaceMeta?.totalGenerations || 0,
totalConversations: conversationsSnapshot.size,
scoreBreakdown: {
negative: negative.length,
neutral,
positive: positive.length
},
negativeScoreConversations: negative,
topPositiveConversations: positive.slice(0, 10),
sampleNeutralConversations: conversationsSnapshot.docs
.filter(doc => (doc.data().relevanceScore || 0) === 0)
.slice(0, 10)
.map(doc => doc.data().name)
});
} catch (error) {
console.error('Error analyzing conversations:', error);
return NextResponse.json(
{ error: 'Failed to analyze', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,72 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get 5 random conversations with content
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.limit(5)
.get();
const samples = conversationsSnapshot.docs.map(doc => {
const data = doc.data();
return {
name: data.name,
messageCount: data.messageCount || 0,
promptCount: data.prompts?.length || 0,
generationCount: data.generations?.length || 0,
filesCount: data.files?.length || 0,
sampleFiles: (data.files || []).slice(0, 3),
samplePrompt: data.prompts?.[0]?.text?.substring(0, 100) || 'none',
hasContent: !!(data.prompts?.length || data.generations?.length)
};
});
// Get overall stats
const allConversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.get();
let totalWithContent = 0;
let totalWithFiles = 0;
let totalMessages = 0;
allConversationsSnapshot.docs.forEach(doc => {
const data = doc.data();
if (data.prompts?.length || data.generations?.length) {
totalWithContent++;
}
if (data.files?.length) {
totalWithFiles++;
}
totalMessages += data.messageCount || 0;
});
return NextResponse.json({
totalConversations: allConversationsSnapshot.size,
totalWithContent,
totalWithFiles,
totalMessages,
samples
});
} catch (error) {
console.error('Error fetching content sample:', error);
return NextResponse.json(
{ error: 'Failed to fetch sample', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,55 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get cursor conversations for this project
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.orderBy('createdAt', 'desc')
.get();
const conversations = conversationsSnapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
// Get the messages data
const messagesDoc = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorData')
.doc('messages')
.get();
const messagesData = messagesDoc.exists ? messagesDoc.data() : null;
return NextResponse.json({
projectId,
conversationCount: conversations.length,
conversations,
messagesData: messagesData ? {
promptCount: messagesData.prompts?.length || 0,
generationCount: messagesData.generations?.length || 0,
importedAt: messagesData.importedAt
} : null
});
} catch (error) {
console.error('Error fetching cursor conversations:', error);
return NextResponse.json(
{ error: 'Failed to fetch conversations', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,56 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
const minScore = parseInt(request.nextUrl.searchParams.get('minScore') || '0');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all conversations
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.get();
const conversations = conversationsSnapshot.docs
.map(doc => {
const data = doc.data();
return {
name: data.name,
relevanceScore: data.relevanceScore || 0,
createdAt: data.createdAt,
workspacePath: data.workspacePath
};
})
.filter(c => c.relevanceScore >= minScore)
.sort((a, b) => b.relevanceScore - a.relevanceScore);
// Group by score
const scoreGroups: Record<number, number> = {};
conversationsSnapshot.docs.forEach(doc => {
const score = doc.data().relevanceScore || 0;
scoreGroups[score] = (scoreGroups[score] || 0) + 1;
});
return NextResponse.json({
totalConversations: conversationsSnapshot.size,
filteredConversations: conversations.length,
minScore,
scoreDistribution: scoreGroups,
conversations: conversations.slice(0, 50) // First 50
});
} catch (error) {
console.error('Error fetching relevant conversations:', error);
return NextResponse.json(
{ error: 'Failed to fetch conversations', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,41 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get 10 conversations with their dates
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.orderBy('createdAt', 'desc')
.limit(10)
.get();
const samples = conversationsSnapshot.docs.map(doc => {
const data = doc.data();
return {
name: data.name,
createdAt: data.createdAt,
workspacePath: data.workspacePath
};
});
return NextResponse.json({ samples });
} catch (error) {
console.error('Error fetching samples:', error);
return NextResponse.json(
{ error: 'Failed to fetch samples', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,55 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all conversations sorted by time
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.orderBy('createdAt', 'asc')
.get();
const conversations = conversationsSnapshot.docs.map(doc => {
const data = doc.data();
return {
name: data.name,
createdAt: new Date(data.createdAt),
score: data.relevanceScore || 0
};
});
// Find NHL work sessions (negative scores clustered in time)
const nhlConversations = conversations.filter(c => c.score < 0);
return NextResponse.json({
totalConversations: conversations.length,
nhlConversations: nhlConversations.length,
nhlDates: nhlConversations.map(c => ({
date: c.createdAt.toISOString().split('T')[0],
name: c.name
})),
// Find if NHL conversations cluster
nhlDateCounts: nhlConversations.reduce((acc: any, c) => {
const date = c.createdAt.toISOString().split('T')[0];
acc[date] = (acc[date] || 0) + 1;
return acc;
}, {})
});
} catch (error) {
console.error('Error analyzing session summary:', error);
return NextResponse.json(
{ error: 'Failed to analyze', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,124 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
const sessionGapMinutes = parseInt(request.nextUrl.searchParams.get('gap') || '120'); // 2 hours default
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all conversations sorted by time
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.orderBy('createdAt', 'asc')
.get();
const conversations = conversationsSnapshot.docs.map(doc => {
const data = doc.data();
return {
id: doc.id,
name: data.name,
createdAt: new Date(data.createdAt),
relevanceScore: data.relevanceScore || 0
};
});
// Group into sessions based on time gaps
const sessions: any[] = [];
let currentSession: any = null;
for (const conv of conversations) {
if (!currentSession) {
// Start first session
currentSession = {
startTime: conv.createdAt,
endTime: conv.createdAt,
conversations: [conv],
relevanceScores: [conv.relevanceScore]
};
} else {
// Check time gap from last conversation
const gapMs = conv.createdAt.getTime() - currentSession.endTime.getTime();
const gapMinutes = gapMs / (1000 * 60);
if (gapMinutes <= sessionGapMinutes) {
// Same session
currentSession.conversations.push(conv);
currentSession.relevanceScores.push(conv.relevanceScore);
currentSession.endTime = conv.createdAt;
} else {
// New session - close current and start new
sessions.push(currentSession);
currentSession = {
startTime: conv.createdAt,
endTime: conv.createdAt,
conversations: [conv],
relevanceScores: [conv.relevanceScore]
};
}
}
}
// Add last session
if (currentSession) {
sessions.push(currentSession);
}
// Analyze each session
const analyzedSessions = sessions.map((session, idx) => {
const durationMinutes = Math.round((session.endTime.getTime() - session.startTime.getTime()) / (1000 * 60));
// Calculate session relevance score (average of all conversations)
const avgScore = session.relevanceScores.reduce((a: number, b: number) => a + b, 0) / session.relevanceScores.length;
// Count negative/positive conversations
const negative = session.relevanceScores.filter((s: number) => s < 0).length;
const positive = session.relevanceScores.filter((s: number) => s > 0).length;
const neutral = session.relevanceScores.filter((s: number) => s === 0).length;
// Determine likely project based on majority
let likelyProject = 'unknown';
if (negative > positive && negative > neutral) {
likelyProject = 'other (NHL/market)';
} else if (positive > negative && positive > neutral) {
likelyProject = 'vibn (likely)';
} else if (positive > 0 || avgScore > 0) {
likelyProject = 'vibn (mixed)';
} else {
likelyProject = 'unclear';
}
return {
sessionNumber: idx + 1,
startTime: session.startTime.toISOString(),
endTime: session.endTime.toISOString(),
durationMinutes,
conversationCount: session.conversations.length,
avgRelevanceScore: Math.round(avgScore * 100) / 100,
scoreBreakdown: { negative, neutral, positive },
likelyProject,
conversationNames: session.conversations.slice(0, 5).map((c: any) => c.name)
};
});
return NextResponse.json({
totalConversations: conversations.length,
totalSessions: sessions.length,
sessionGapMinutes,
sessions: analyzedSessions
});
} catch (error) {
console.error('Error analyzing sessions:', error);
return NextResponse.json(
{ error: 'Failed to analyze sessions', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,69 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all conversations
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.get();
const conversations = conversationsSnapshot.docs.map(doc => doc.data());
// Find date range
const dates = conversations
.filter(c => c.createdAt)
.map(c => new Date(c.createdAt))
.sort((a, b) => a.getTime() - b.getTime());
if (dates.length === 0) {
return NextResponse.json({ error: 'No conversations with dates found' });
}
const earliest = dates[0];
const latest = dates[dates.length - 1];
const span = Math.floor((latest.getTime() - earliest.getTime()) / (1000 * 60 * 60 * 24));
// Find the actual conversation names for earliest and latest
const earliestConv = conversations.find(c =>
new Date(c.createdAt).getTime() === earliest.getTime()
);
const latestConv = conversations.find(c =>
new Date(c.createdAt).getTime() === latest.getTime()
);
return NextResponse.json({
totalConversations: conversations.length,
dateRange: {
earliest: earliest.toISOString(),
latest: latest.toISOString(),
spanDays: span
},
oldestConversation: {
name: earliestConv?.name || 'Unknown',
date: earliest.toISOString()
},
newestConversation: {
name: latestConv?.name || 'Unknown',
date: latest.toISOString()
}
});
} catch (error) {
console.error('Error fetching cursor stats:', error);
return NextResponse.json(
{ error: 'Failed to fetch stats', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all unknown conversations grouped by session
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.where('sessionProject', '==', 'unknown')
.get();
const sessionMap: Record<number, any[]> = {};
conversationsSnapshot.docs.forEach(doc => {
const data = doc.data();
const sessionId = data.sessionId;
if (!sessionMap[sessionId]) {
sessionMap[sessionId] = [];
}
sessionMap[sessionId].push({
name: data.name,
date: data.sessionDate,
createdAt: data.createdAt
});
});
// Convert to array and take sample
const sessions = Object.entries(sessionMap).map(([sessionId, conversations]) => ({
sessionId: parseInt(sessionId),
date: conversations[0].date,
conversationCount: conversations.length,
conversationNames: conversations.map(c => c.name)
}));
return NextResponse.json({
totalUnknownSessions: sessions.length,
totalUnknownConversations: conversationsSnapshot.size,
sample: sessions.slice(0, 30)
});
} catch (error) {
console.error('Error fetching unknown sessions:', error);
return NextResponse.json(
{ error: 'Failed to fetch', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from 'next/server';
import { adminDb } from '@/lib/firebase/admin';
export async function GET(request: NextRequest) {
try {
const projectId = request.nextUrl.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
// Get all conversations
const conversationsSnapshot = await adminDb
.collection('projects')
.doc(projectId)
.collection('cursorConversations')
.get();
const conversations = conversationsSnapshot.docs.map(doc => doc.data());
// Group by workspace path
const workspaceGroups: Record<string, number> = {};
const githubGroups: Record<string, number> = {};
conversations.forEach(conv => {
const workspace = conv.workspacePath || 'unknown';
const github = conv.githubUrl || 'none';
workspaceGroups[workspace] = (workspaceGroups[workspace] || 0) + 1;
githubGroups[github] = (githubGroups[github] || 0) + 1;
});
// Sort by count
const workspaceList = Object.entries(workspaceGroups)
.map(([path, count]) => ({ path, count }))
.sort((a, b) => b.count - a.count);
const githubList = Object.entries(githubGroups)
.map(([url, count]) => ({ url, count }))
.sort((a, b) => b.count - a.count);
return NextResponse.json({
totalConversations: conversations.length,
uniqueWorkspaces: workspaceList.length,
uniqueRepos: githubList.length,
workspaces: workspaceList,
repos: githubList
});
} catch (error) {
console.error('Error fetching workspace breakdown:', error);
return NextResponse.json(
{ error: 'Failed to fetch breakdown', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
);
}
}

18
app/api/debug/env/route.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({
firebaseProjectId: process.env.FIREBASE_PROJECT_ID ? 'SET' : 'NOT SET',
firebaseClientEmail: process.env.FIREBASE_CLIENT_EMAIL ? 'SET' : 'NOT SET',
firebasePrivateKey: process.env.FIREBASE_PRIVATE_KEY ? 'SET (length: ' + process.env.FIREBASE_PRIVATE_KEY.length + ')' : 'NOT SET',
publicApiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY ? 'SET' : 'NOT SET',
publicAuthDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN ? 'SET' : 'NOT SET',
publicProjectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID ? 'SET' : 'NOT SET',
nodeEnv: process.env.NODE_ENV,
tip: 'If any Firebase vars show NOT SET, restart your dev server after updating .env.local'
});
}

View File

@@ -0,0 +1,33 @@
import { NextResponse } from 'next/server';
import { getAdminDb } from '@/lib/firebase/admin';
export async function GET() {
try {
const adminDb = getAdminDb();
const snapshot = await adminDb.collection('projects').limit(1).get();
if (snapshot.empty) {
return NextResponse.json(
{ error: 'No projects found' },
{ status: 404 },
);
}
const doc = snapshot.docs[0];
return NextResponse.json({
id: doc.id,
data: doc.data(),
});
} catch (error) {
console.error('[debug/first-project] Failed to load project', error);
return NextResponse.json(
{
error: 'Failed to load project',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,43 @@
import { NextResponse } from 'next/server';
import { getAdminDb } from '@/lib/firebase/admin';
export async function GET(request: Request) {
try {
const url = new URL(request.url);
const projectId = url.searchParams.get('projectId');
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
const adminDb = getAdminDb();
// Get knowledge_items collection
const knowledgeItemsRef = adminDb.collection('knowledge_items');
const snapshot = await knowledgeItemsRef
.where('projectId', '==', projectId)
.limit(20)
.get();
const items = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}));
return NextResponse.json({
projectId,
count: items.length,
items,
});
} catch (error) {
console.error('[debug/knowledge-items] Error:', error);
return NextResponse.json(
{
error: 'Failed to fetch knowledge items',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
);
}
}

View File

@@ -0,0 +1,36 @@
import { NextResponse } from 'next/server';
import { getAdminDb } from '@/lib/firebase/admin';
export async function GET(request: Request) {
try {
const url = new URL(request.url);
const projectId = (url.searchParams.get('projectId') ?? '').trim();
if (!projectId) {
return NextResponse.json({ error: 'Missing projectId' }, { status: 400 });
}
const adminDb = getAdminDb();
// Use a simple filter query without orderBy to avoid requiring a composite index.
const snapshot = await adminDb
.collection('knowledge_items')
.where('projectId', '==', projectId)
.get();
const items = snapshot.docs.map((doc) => doc.data());
return NextResponse.json({ count: items.length, items });
} catch (error) {
console.error('[debug/knowledge] Failed to list knowledge items', error);
return NextResponse.json(
{
error: 'Failed to list knowledge items',
details: error instanceof Error ? error.message : String(error),
},
{ status: 500 },
);
}
}