Files
vibn-frontend/app/api/projects/[projectId]/workspace/route.ts
Mark Henderson aa2f5dbc3a feat: add Provision IDE button for projects without a workspace
- POST /api/projects/[id]/workspace: provisions a Cloud Run Theia service
  on demand and saves the URL to the project record
- overview/page.tsx: shows 'Provision IDE' button when theiaWorkspaceUrl
  is null, 'Open IDE' link once provisioned
- Also fixes log spam: retired Firebase session tracking endpoint (410 Gone)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 15:36:18 -08:00

72 lines
2.2 KiB
TypeScript

import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth/authOptions';
import { query } from '@/lib/db-postgres';
import { provisionTheiaWorkspace } from '@/lib/cloud-run-workspace';
export async function POST(
_request: Request,
{ params }: { params: Promise<{ projectId: string }> },
) {
try {
const { projectId } = await params;
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// Verify ownership
const rows = await query<{ id: string; data: any }>(`
SELECT p.id, p.data
FROM fs_projects p
JOIN fs_users u ON u.id = p.user_id
WHERE p.id = $1 AND u.data->>'email' = $2
LIMIT 1
`, [projectId, session.user.email]);
if (rows.length === 0) {
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
}
const project = rows[0].data;
if (project.theiaWorkspaceUrl) {
return NextResponse.json({
success: true,
workspaceUrl: project.theiaWorkspaceUrl,
message: 'Workspace already provisioned',
});
}
const slug = project.slug;
if (!slug) {
return NextResponse.json({ error: 'Project has no slug — cannot provision workspace' }, { status: 400 });
}
// Provision Cloud Run workspace
const workspace = await provisionTheiaWorkspace(slug, projectId, project.giteaRepo ?? null);
// Save URL back to project record
await query(`
UPDATE fs_projects
SET data = data || jsonb_build_object(
'theiaWorkspaceUrl', $1::text,
'theiaAppUuid', $2::text
)
WHERE id = $3
`, [workspace.serviceUrl, workspace.serviceName, projectId]);
return NextResponse.json({
success: true,
workspaceUrl: workspace.serviceUrl,
});
} catch (error) {
console.error('[POST /api/projects/:id/workspace] Error:', error);
return NextResponse.json(
{ error: 'Failed to provision workspace', details: error instanceof Error ? error.message : String(error) },
{ status: 500 },
);
}
}