From 710a24a2fbf6285687208ea2100fd400c61a9c3a Mon Sep 17 00:00:00 2001 From: mark Date: Wed, 18 Feb 2026 01:24:47 +0000 Subject: [PATCH] feat: rewrite project create to use NextAuth session + Postgres --- app/api/projects/create/route.ts | 141 +++++++++++++------------------ 1 file changed, 57 insertions(+), 84 deletions(-) diff --git a/app/api/projects/create/route.ts b/app/api/projects/create/route.ts index a1802c4..26c7989 100644 --- a/app/api/projects/create/route.ts +++ b/app/api/projects/create/route.ts @@ -1,26 +1,29 @@ import { NextResponse } from 'next/server'; -import { getAdminAuth, getAdminDb } from '@/lib/firebase/admin'; -import { FieldValue } from 'firebase-admin/firestore'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/auth/authOptions'; +import { query } from '@/lib/db-postgres'; +import { randomUUID } from 'crypto'; import type { ProjectPhaseData, ProjectPhaseScores } from '@/lib/types/project-artifacts'; export async function POST(request: Request) { try { - const authHeader = request.headers.get('Authorization'); - if (!authHeader?.startsWith('Bearer ')) { + const session = await getServerSession(authOptions); + if (!session?.user?.email) { 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 email = session.user.email; + + // Resolve Firebase user ID and workspace from fs_users + const users = await query<{ id: string; data: any }>(` + SELECT id, data FROM fs_users WHERE data->>'email' = $1 LIMIT 1 + `, [email]); + + const firebaseUserId = users[0]?.id || session.user.id || randomUUID(); + const userData = users[0]?.data || {}; + const workspace = + userData.workspace || + email.split('@')[0].toLowerCase().replace(/[^a-z0-9]+/g, '-') + '-account'; const body = await request.json(); const { @@ -29,111 +32,81 @@ export async function POST(request: Request) { slug, vision, product, - workspacePath, // Optional: if coming from association prompt - chatgptUrl, // Optional: if from ChatGPT - githubRepo, // Optional: if from GitHub + workspacePath, + chatgptUrl, + githubRepo, githubRepoId, githubRepoUrl, githubDefaultBranch, } = body; - // Check if slug is available - const existingProject = await adminDb - .collection('projects') - .where('slug', '==', slug) - .limit(1) - .get(); - - if (!existingProject.empty) { - return NextResponse.json( - { error: 'Project slug already exists' }, - { status: 400 } - ); + // Check slug uniqueness + const existing = await query(`SELECT id FROM fs_projects WHERE slug = $1 LIMIT 1`, [slug]); + if (existing.length > 0) { + return NextResponse.json({ error: 'Project slug already exists' }, { status: 400 }); } - // Get user data - const userDoc = await adminDb.collection('users').doc(userId).get(); - const userData = userDoc.data(); - const workspace = userData?.workspace || 'my-workspace'; + const projectId = randomUUID(); + const now = new Date().toISOString(); - // Create project - const projectRef = adminDb.collection('projects').doc(); - await projectRef.set({ - id: projectRef.id, + const projectData = { + id: projectId, name: projectName, slug, - userId, + userId: firebaseUserId, workspace, projectType, - productName: product.name, + productName: product?.name || projectName, productVision: vision || '', - isForClient: product.isForClient || false, - hasLogo: product.hasLogo || false, - hasDomain: product.hasDomain || false, - hasWebsite: product.hasWebsite || false, + isForClient: product?.isForClient || false, + hasLogo: product?.hasLogo || false, + hasDomain: product?.hasDomain || false, + hasWebsite: product?.hasWebsite || false, hasGithub: !!githubRepo, hasChatGPT: !!chatgptUrl, workspacePath: workspacePath || null, workspaceName: workspacePath ? workspacePath.split('/').pop() : null, - // GitHub data githubRepo: githubRepo || null, githubRepoId: githubRepoId || null, githubRepoUrl: githubRepoUrl || null, githubDefaultBranch: githubDefaultBranch || null, - // ChatGPT data chatgptUrl: chatgptUrl || null, - // Extension tracking extensionLinked: false, status: 'active', - // Pipeline tracking currentPhase: 'collector', phaseStatus: 'not_started', phaseData: {} as ProjectPhaseData, phaseScores: {} as ProjectPhaseScores, - createdAt: FieldValue.serverTimestamp(), - updatedAt: FieldValue.serverTimestamp(), - }); + createdAt: now, + updatedAt: now, + }; - console.log(`[API] Created project ${projectRef.id} (${slug})`); + await query(` + INSERT INTO fs_projects (id, data, user_id, workspace, slug) + VALUES ($1, $2::jsonb, $3, $4, $5) + `, [projectId, JSON.stringify(projectData), firebaseUserId, workspace, slug]); - // If workspacePath provided, associate existing sessions + // Associate unlinked sessions for this workspace path if (workspacePath) { - const sessionsSnapshot = await adminDb - .collection('sessions') - .where('userId', '==', userId) - .where('workspacePath', '==', workspacePath) - .where('needsProjectAssociation', '==', true) - .get(); - - if (!sessionsSnapshot.empty) { - const batch = adminDb.batch(); - sessionsSnapshot.docs.forEach((doc) => { - batch.update(doc.ref, { - projectId: projectRef.id, - needsProjectAssociation: false, - updatedAt: FieldValue.serverTimestamp(), - }); - }); - await batch.commit(); - console.log(`[API] Associated ${sessionsSnapshot.size} sessions with project`); - } + await query(` + UPDATE fs_sessions + SET data = jsonb_set( + jsonb_set(data, '{projectId}', $1::jsonb), + '{needsProjectAssociation}', 'false' + ) + WHERE user_id = $2 + AND data->>'workspacePath' = $3 + AND (data->>'needsProjectAssociation')::boolean = true + `, [JSON.stringify(projectId), firebaseUserId, workspacePath]); } - return NextResponse.json({ - success: true, - projectId: projectRef.id, - slug, - workspace, - }); + console.log('[API] Created project', projectId, slug); + return NextResponse.json({ success: true, projectId, slug, workspace }); } catch (error) { - console.error('Error creating project:', error); + console.error('[POST /api/projects/create] Error:', error); return NextResponse.json( - { - error: 'Failed to create project', - details: error instanceof Error ? error.message : String(error), - }, + { error: 'Failed to create project', details: error instanceof Error ? error.message : String(error) }, { status: 500 } ); } } -