import { NextResponse } from 'next/server'; import { and, eq, desc, inArray } from 'drizzle-orm'; import { withAuth, withPermission } from '@/lib/api/helpers'; import { errorResponse, NotFoundError } from '@/lib/errors'; import { db } from '@/lib/db'; import { interests } from '@/lib/db/schema/interests'; import { auditLogs } from '@/lib/db/schema/system'; import { documents, documentEvents } from '@/lib/db/schema/documents'; interface TimelineEvent { id: string; type: 'audit' | 'document_event'; action: string; description: string; userId: string | null; createdAt: Date; metadata: Record; } // GET /api/v1/interests/[id]/timeline export const GET = withAuth( withPermission('interests', 'view', async (req, ctx, params) => { try { const interestId = params.id!; const interest = await db.query.interests.findFirst({ where: and(eq(interests.id, interestId), eq(interests.portId, ctx.portId)), }); if (!interest) throw new NotFoundError('Interest'); // Fetch audit logs for this interest const auditRows = await db .select() .from(auditLogs) .where( and( eq(auditLogs.entityType, 'interest'), eq(auditLogs.entityId, interestId), ), ) .orderBy(desc(auditLogs.createdAt)) .limit(50); // Fetch document events for documents linked to this interest const interestDocs = await db .select({ id: documents.id, title: documents.title }) .from(documents) .where(eq(documents.interestId, interestId)); const docIds = interestDocs.map((d) => d.id); const docEventRows = docIds.length > 0 ? await db .select({ id: documentEvents.id, documentId: documentEvents.documentId, eventType: documentEvents.eventType, eventData: documentEvents.eventData, createdAt: documentEvents.createdAt, }) .from(documentEvents) .where(inArray(documentEvents.documentId, docIds)) .orderBy(desc(documentEvents.createdAt)) .limit(50) : []; const docTitles = Object.fromEntries(interestDocs.map((d) => [d.id, d.title])); // Union and sort const auditEvents: TimelineEvent[] = auditRows.map((row) => ({ id: row.id, type: 'audit', action: row.action, description: buildAuditDescription(row.action, row.newValue as Record | null), userId: row.userId, createdAt: row.createdAt, metadata: (row.metadata as Record) ?? {}, })); const docEvents: TimelineEvent[] = docEventRows.map((row) => ({ id: row.id, type: 'document_event', action: row.eventType, description: `Document "${docTitles[row.documentId] ?? row.documentId}": ${row.eventType}`, userId: null, createdAt: row.createdAt, metadata: (row.eventData as Record) ?? {}, })); const allEvents = [...auditEvents, ...docEvents]; allEvents.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); return NextResponse.json({ data: allEvents.slice(0, 50) }); } catch (error) { return errorResponse(error); } }), ); function buildAuditDescription( action: string, newValue: Record | null, ): string { if (action === 'create') return 'Interest created'; if (action === 'archive') return 'Interest archived'; if (action === 'restore') return 'Interest restored'; if (action === 'update' && newValue?.pipelineStage) { return `Stage changed to "${newValue.pipelineStage}"`; } if (action === 'update') return 'Interest updated'; return action; }