Add missing /api/activity route for workspace activity page
The workspace Activity page (/[workspace]/activity) was calling /api/activity which did not exist, so the feed was always empty. New route aggregates agent_sessions (builds/deploys) and fs_projects (creation/status changes) across all user projects, returning ActivityItem[] sorted by date descending. Made-with: Cursor
This commit is contained in:
124
app/api/activity/route.ts
Normal file
124
app/api/activity/route.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* 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<any>(
|
||||
`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<any>(
|
||||
`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: [] });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user