/** * GET /api/activity * * Workspace-wide activity feed. Aggregates recent events across all of the * authenticated user's projects: agent sessions (builds), Coolify deployments, * and project creation/updates. * * Returns ActivityItem[] shaped for the workspace Activity page. */ import { NextResponse } from 'next/server'; import { authSession } from '@/lib/auth/session-server'; import { query } from '@/lib/db-postgres'; export const dynamic = 'force-dynamic'; interface ActivityItem { id: string; projectId: string; projectName: string; action: string; type: 'atlas' | 'build' | 'deploy' | 'user'; createdAt: string; } export async function GET() { const session = await authSession(); if (!session?.user?.email) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } const email = session.user.email; const items: ActivityItem[] = []; try { // --- Agent sessions (build / deploy events) --- const agentRows = await query( `SELECT a.id, a.project_id, a.app_name, a.status, a.task, a.created_at, a.started_at, a.completed_at, p.data->>'productName' AS project_name, p.data->>'name' AS project_name_fallback FROM agent_sessions a JOIN fs_projects p ON p.id = a.project_id WHERE p.user_id = $1 ORDER BY a.created_at DESC LIMIT 60`, [email], ).catch(() => []); for (const r of agentRows) { const name = r.project_name || r.project_name_fallback || 'Untitled'; const status = r.status as string; const type: ActivityItem['type'] = status === 'completed' || status === 'failed' ? 'deploy' : 'build'; const verb = status === 'completed' ? 'Deployed' : status === 'failed' ? 'Deploy failed for' : status === 'running' ? 'Building' : 'Queued build for'; items.push({ id: `agent-${r.id}`, projectId: r.project_id, projectName: name, action: `${verb} ${r.app_name || 'app'}`, type, createdAt: (r.completed_at || r.started_at || r.created_at).toISOString(), }); } // --- Project creation / significant updates --- const projectRows = await query( `SELECT id, data, created_at, updated_at FROM fs_projects WHERE user_id = $1 ORDER BY created_at DESC LIMIT 40`, [email], ).catch(() => []); for (const r of projectRows) { const name = r.data?.productName || r.data?.name || 'Untitled'; items.push({ id: `project-created-${r.id}`, projectId: r.id, projectName: name, action: `Created project "${name}"`, type: 'user', createdAt: r.created_at.toISOString(), }); // If there's a notable status change in data, surface it const status = r.data?.status; if (status && status !== 'defining') { items.push({ id: `project-status-${r.id}`, projectId: r.id, projectName: name, action: `Project moved to "${status}"`, type: 'atlas', createdAt: r.updated_at.toISOString(), }); } } // Sort all items by date descending, cap at 80 items.sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), ); return NextResponse.json({ items: items.slice(0, 80) }); } catch (err) { console.error('[/api/activity]', err); return NextResponse.json({ items: [] }); } }