import { db } from '@sim/db' import { workflowExecutionLogs, workflowExecutionSnapshots } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { v1GetExecutionContract } from '@/lib/api/contracts/v1/logs' import { parseRequest } from '@/lib/api/server' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { createApiResponse, getUserLimits } from '@/app/api/v1/logs/meta' import { checkRateLimit, createRateLimitResponse, validateWorkspaceAccess, } from '@/app/api/v1/middleware' const logger = createLogger('V1ExecutionAPI') export const GET = withRouteHandler( async (request: NextRequest, context: { params: Promise<{ executionId: string }> }) => { try { const rateLimit = await checkRateLimit(request, 'logs-detail') if (!rateLimit.allowed) { return createRateLimitResponse(rateLimit) } const userId = rateLimit.userId! const parsed = await parseRequest(v1GetExecutionContract, request, context, { validationErrorResponse: () => NextResponse.json({ error: 'Invalid execution ID' }, { status: 400 }), }) if (!parsed.success) return parsed.response const { executionId } = parsed.data.params logger.debug(`Fetching execution data for: ${executionId}`) const rows = await db .select() .from(workflowExecutionLogs) .where(eq(workflowExecutionLogs.executionId, executionId)) .limit(1) if (rows.length === 0) { return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 }) } const workflowLog = rows[0] const accessError = await validateWorkspaceAccess(rateLimit, userId, workflowLog.workspaceId) if (accessError) { return NextResponse.json({ error: 'Workflow execution not found' }, { status: 404 }) } const [snapshot] = await db .select() .from(workflowExecutionSnapshots) .where(eq(workflowExecutionSnapshots.id, workflowLog.stateSnapshotId)) .limit(1) if (!snapshot) { return NextResponse.json({ error: 'Workflow state snapshot not found' }, { status: 404 }) } const response = { executionId, workflowId: workflowLog.workflowId, workflowState: snapshot.stateData, executionMetadata: { trigger: workflowLog.trigger, startedAt: workflowLog.startedAt.toISOString(), endedAt: workflowLog.endedAt?.toISOString(), totalDurationMs: workflowLog.totalDurationMs, // Sourced from the cost_total projection of the usage_log ledger // (the deprecated cost jsonb column was dropped). cost: workflowLog.costTotal != null ? { total: Number(workflowLog.costTotal) } : null, }, } logger.debug(`Successfully fetched execution data for: ${executionId}`) logger.debug( `Workflow state contains ${Object.keys((snapshot.stateData as any)?.blocks || {}).length} blocks` ) // Get user's workflow execution limits and usage const limits = await getUserLimits(userId) // Create response with limits information const apiResponse = createApiResponse( { ...response, }, limits, rateLimit ) return NextResponse.json(apiResponse.body, { headers: apiResponse.headers }) } catch (error) { logger.error('Error fetching execution data:', error) return NextResponse.json({ error: 'Failed to fetch execution data' }, { status: 500 }) } } )