/** * POST /api/projects/[projectId]/agent/sessions/[sessionId]/approve * * Called by the frontend when the user clicks "Approve & commit". * Verifies ownership, then asks the agent runner to git commit + push * the changes it made in the workspace, and triggers a Coolify deploy. * * Body: { commitMessage: string } */ import { NextResponse } from "next/server"; import { getServerSession } from "next-auth"; import { authOptions } from "@/lib/auth/authOptions"; import { query } from "@/lib/db-postgres"; const AGENT_RUNNER_URL = process.env.AGENT_RUNNER_URL ?? "http://localhost:3333"; const COOLIFY_API_URL = process.env.COOLIFY_API_URL ?? ""; const COOLIFY_API_TOKEN = process.env.COOLIFY_API_TOKEN ?? ""; interface AppEntry { name: string; path: string; coolifyServiceUuid?: string | null; domain?: string | null; } export async function POST( req: Request, { params }: { params: Promise<{ projectId: string; sessionId: string }> } ) { try { const { projectId, sessionId } = await params; const session = await getServerSession(authOptions); if (!session?.user?.email) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const body = await req.json() as { commitMessage?: string }; const commitMessage = body.commitMessage?.trim(); if (!commitMessage) { return NextResponse.json({ error: "commitMessage is required" }, { status: 400 }); } // Verify ownership + fetch project data (giteaRepo, apps list) const rows = await query<{ data: Record }>( `SELECT p.data FROM fs_projects p JOIN fs_users u ON u.id = p.user_id WHERE p.id = $1::uuid 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 projectData = rows[0].data; const giteaRepo = projectData?.giteaRepo as string | undefined; if (!giteaRepo) { return NextResponse.json({ error: "No Gitea repo linked to this project" }, { status: 400 }); } // Find the session to get the appName (so we can find the right Coolify UUID) const sessionRows = await query<{ app_name: string; status: string }>( `SELECT app_name, status FROM agent_sessions WHERE id = $1::uuid AND project_id = $2::uuid LIMIT 1`, [sessionId, projectId] ); if (sessionRows.length === 0) { return NextResponse.json({ error: "Session not found" }, { status: 404 }); } if (sessionRows[0].status !== "done") { return NextResponse.json({ error: "Session must be in 'done' state to approve" }, { status: 400 }); } const appName = sessionRows[0].app_name; // Find the matching Coolify UUID from project.data.apps[] const apps: AppEntry[] = (projectData?.apps ?? []) as AppEntry[]; const matchedApp = apps.find(a => a.name === appName); const coolifyAppUuid = matchedApp?.coolifyServiceUuid ?? undefined; // Call agent runner to commit + push const approveRes = await fetch(`${AGENT_RUNNER_URL}/agent/approve`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ giteaRepo, commitMessage, coolifyApiUrl: COOLIFY_API_URL, coolifyApiToken: COOLIFY_API_TOKEN, coolifyAppUuid, }), }); const approveData = await approveRes.json() as { ok: boolean; committed?: boolean; deployed?: boolean; message?: string; error?: string; }; if (!approveRes.ok || !approveData.ok) { return NextResponse.json( { error: approveData.error ?? "Agent runner returned an error" }, { status: 500 } ); } // Mark session as approved in DB await query( `UPDATE agent_sessions SET status = 'approved', completed_at = COALESCE(completed_at, now()), updated_at = now(), output = output || $1::jsonb WHERE id = $2::uuid`, [ JSON.stringify([{ ts: new Date().toISOString(), type: "done", text: `✓ ${approveData.message ?? "Committed and pushed."}${approveData.deployed ? " Deployment triggered." : ""}`, }]), sessionId, ] ); return NextResponse.json({ ok: true, committed: approveData.committed, deployed: approveData.deployed, message: approveData.message, }); } catch (err) { console.error("[agent/approve]", err); return NextResponse.json({ error: "Failed to approve session" }, { status: 500 }); } }