import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@clerk/nextjs/server'; import { db, projects, users, tools, apps, apiKeys, deployments } from '@mcpengine/db'; import { eq, and } from 'drizzle-orm'; type RouteContext = { params: Promise<{ id: string }> }; // ── GET /api/projects/[id] — project detail with tools + apps ─────────────── export async function GET(req: NextRequest, context: RouteContext) { try { const { userId: clerkId } = await auth(); if (!clerkId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const user = await db.query.users.findFirst({ where: eq(users.clerkId, clerkId), }); if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }); } const { id } = await context.params; const project = await db.query.projects.findFirst({ where: and(eq(projects.id, id), eq(projects.userId, user.id)), with: { tools: true, apps: true, deployments: { orderBy: (d: any, { desc }: any) => [desc(d.createdAt)], limit: 5, }, }, }); if (!project) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } return NextResponse.json({ data: project }); } catch (error) { console.error('[GET /api/projects/[id]]', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 }, ); } } // ── PATCH /api/projects/[id] — update project ────────────────────────────── export async function PATCH(req: NextRequest, context: RouteContext) { try { const { userId: clerkId } = await auth(); if (!clerkId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const user = await db.query.users.findFirst({ where: eq(users.clerkId, clerkId), }); if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }); } const { id } = await context.params; // Verify ownership const existing = await db.query.projects.findFirst({ where: and(eq(projects.id, id), eq(projects.userId, user.id)), }); if (!existing) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } const body = await req.json(); // Whitelist updatable fields const allowedFields = [ 'name', 'description', 'status', 'specUrl', 'specRaw', 'analysis', 'toolConfig', 'appConfig', 'authConfig', 'serverBundle', ] as const; const updates: Record = { updatedAt: new Date() }; for (const field of allowedFields) { if (body[field] !== undefined) { updates[field] = body[field]; } } const [updated] = (await db .update(projects) .set(updates) .where(and(eq(projects.id, id), eq(projects.userId, user.id))) .returning()) as any[]; return NextResponse.json({ data: updated }); } catch (error) { console.error('[PATCH /api/projects/[id]]', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 }, ); } } // ── DELETE /api/projects/[id] — delete project + cascade ──────────────────── export async function DELETE(req: NextRequest, context: RouteContext) { try { const { userId: clerkId } = await auth(); if (!clerkId) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const user = await db.query.users.findFirst({ where: eq(users.clerkId, clerkId), }); if (!user) { return NextResponse.json({ error: 'User not found' }, { status: 404 }); } const { id } = await context.params; // Verify ownership const existing = await db.query.projects.findFirst({ where: and(eq(projects.id, id), eq(projects.userId, user.id)), }); if (!existing) { return NextResponse.json({ error: 'Project not found' }, { status: 404 }); } // Delete non-cascade relations first await db.delete(deployments).where(eq(deployments.projectId, id)); // tools, apps, apiKeys cascade automatically via FK onDelete await db .delete(projects) .where(and(eq(projects.id, id), eq(projects.userId, user.id))); return NextResponse.json({ success: true }, { status: 200 }); } catch (error) { console.error('[DELETE /api/projects/[id]]', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 }, ); } }