Files
vibn-frontend/app/api/projects/[projectId]/preview-url/route.ts
Mark Henderson 651ddf1e11 Rip out Theia, ship P5.1 attach E2E + Justine UI work-in-progress
Theia rip-out:
- Delete app/api/theia-auth/route.ts (Traefik ForwardAuth shim)
- Delete app/api/projects/[projectId]/workspace/route.ts and
  app/api/projects/prewarm/route.ts (Cloud Run Theia provisioning)
- Delete lib/cloud-run-workspace.ts and lib/coolify-workspace.ts
- Strip provisionTheiaWorkspace + theiaWorkspaceUrl/theiaAppUuid/
  theiaError from app/api/projects/create/route.ts response
- Remove Theia callbackUrl branch in app/auth/page.tsx
- Drop "Open in Theia" button + xterm/Theia PTY copy in build/page.tsx
- Drop theiaWorkspaceUrl from deployment/page.tsx Project type
- Strip Theia IDE line + theia-code-os from advisor + agent-chat
  context strings
- Scrub Theia mention from lib/auth/workspace-auth.ts comment

P5.1 (custom apex domains + DNS):
- lib/coolify.ts + lib/opensrs.ts: nameserver normalization, OpenSRS
  XML auth, Cloud DNS plumbing
- scripts/smoke-attach-e2e.ts: full prod GCP + sandbox OpenSRS +
  prod Coolify smoke covering register/zone/A/NS/PATCH/cleanup

In-progress (Justine onboarding/build, MVP setup, agent telemetry):
- New (justine)/stories, project (home) layouts, mvp-setup, run, tasks
  routes + supporting components
- Project shell + sidebar + nav refactor for the Stackless palette
- Agent session API hardening (sessions, events, stream, approve,
  retry, stop) + atlas-chat, advisor, design-surfaces refresh
- New scripts/sync-db-url-from-coolify.mjs +
  scripts/prisma-db-push.mjs + docker-compose.local-db.yml for
  local Prisma workflows
- lib/dev-bypass.ts, lib/chat-context-refs.ts, lib/prd-sections.ts
- Misc: stories CSS, debug/prisma route, modal-theme, BuildLivePlanPanel

Made-with: Cursor
2026-04-22 18:05:01 -07:00

95 lines
3.0 KiB
TypeScript

import { NextResponse } from 'next/server';
import { authSession } from "@/lib/auth/session-server";
import { query } from '@/lib/db-postgres';
import { listApplications, CoolifyApplication } from '@/lib/coolify';
const GITEA_BASE = process.env.GITEA_API_URL ?? 'https://git.vibnai.com';
export interface PreviewApp {
name: string;
url: string | null;
status: string;
coolifyUuid: string | null;
gitRepo: string | null;
}
export async function GET(
_req: Request,
{ params }: { params: Promise<{ projectId: string }> }
) {
const { projectId } = await params;
const session = await authSession();
if (!session?.user?.email) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 1. Load project — get the Gitea repo name
const rows = await query<{ data: Record<string, unknown> }>(
`SELECT 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 data = rows[0].data ?? {};
const giteaRepo = data.giteaRepo as string | undefined; // e.g. "mark/scout-ai-engine"
if (!giteaRepo) {
return NextResponse.json({ apps: [], message: 'No Gitea repo linked to this project' });
}
// 2. Build the possible Gitea remote URLs for this repo (with and without .git)
const repoBase = `${GITEA_BASE}/${giteaRepo}`;
const repoUrls = new Set([repoBase, `${repoBase}.git`]);
// 3. Fetch all Coolify applications and match by git_repository
let coolifyApps: CoolifyApplication[] = [];
try {
coolifyApps = await listApplications();
} catch (err) {
console.error('[preview-url] Coolify fetch failed:', err);
// Fall back to stored data
}
const matched = coolifyApps.filter(app =>
app.git_repository && repoUrls.has(app.git_repository.replace(/\/$/, ''))
);
if (matched.length > 0) {
const apps: PreviewApp[] = matched.map(app => ({
name: app.name,
url: app.fqdn
? (app.fqdn.startsWith('http') ? app.fqdn : `https://${app.fqdn}`)
: null,
status: app.status ?? 'unknown',
coolifyUuid: app.uuid,
gitRepo: app.git_repository ?? null,
}));
return NextResponse.json({ apps, source: 'coolify' });
}
// 4. Fallback: use whatever URL was stored by the Coolify webhook
const lastDeployment = (data as any).contextSnapshot?.lastDeployment ?? null;
if (lastDeployment?.url) {
const apps: PreviewApp[] = [{
name: giteaRepo.split('/').pop() ?? 'app',
url: lastDeployment.url.startsWith('http') ? lastDeployment.url : `https://${lastDeployment.url}`,
status: lastDeployment.status === 'finished' ? 'running' : lastDeployment.status ?? 'unknown',
coolifyUuid: lastDeployment.applicationUuid ?? null,
gitRepo: giteaRepo,
}];
return NextResponse.json({ apps, source: 'webhook' });
}
return NextResponse.json({
apps: [],
message: `No Coolify app found for repo: ${giteaRepo}`,
giteaRepo,
});
}