=== NEW === - studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan) - docs/FACTORY-V2.md — Factory v2 architecture doc - docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report === UPDATED SERVERS === - fieldedge: Added jobs-tools, UI build script, main entry update - lightspeed: Updated main + server entry points - squarespace: Added collection-browser + page-manager apps - toast: Added main + server entry points === INFRA === - infra/command-center/state.json — Updated pipeline state - infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
161 lines
4.6 KiB
TypeScript
161 lines
4.6 KiB
TypeScript
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<string, unknown> = { 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 },
|
|
);
|
|
}
|
|
}
|