11import { db } from '@sim/db'
22import {
33 jobExecutionLogs ,
4+ pausedExecutions ,
45 permissions ,
56 workflow ,
67 workflowDeploymentVersion ,
@@ -9,175 +10,195 @@ import {
910import { createLogger } from '@sim/logger'
1011import { and , eq } from 'drizzle-orm'
1112import { type NextRequest , NextResponse } from 'next/server'
12- import { logIdParamsSchema } from '@/lib/api/contracts/logs'
13+ import { getLogDetailContract } from '@/lib/api/contracts/logs'
14+ import { parseRequest } from '@/lib/api/server'
1315import { getSession } from '@/lib/auth'
14- import { generateRequestId } from '@/lib/core/utils/request'
1516import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1617
1718const logger = createLogger ( 'LogDetailsByIdAPI' )
1819
19- export const revalidate = 0
20-
2120export const GET = withRouteHandler (
22- async ( _request : NextRequest , { params } : { params : Promise < { id : string } > } ) => {
23- const requestId = generateRequestId ( )
21+ async ( request : NextRequest , context : { params : Promise < { id : string } > } ) => {
22+ const session = await getSession ( )
23+ if ( ! session ?. user ?. id ) {
24+ return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
25+ }
2426
25- try {
26- const session = await getSession ( )
27- if ( ! session ?. user ?. id ) {
28- logger . warn ( `[${ requestId } ] Unauthorized log details access attempt` )
29- return NextResponse . json ( { error : 'Unauthorized' } , { status : 401 } )
30- }
27+ const userId = session . user . id
28+ const parsed = await parseRequest ( getLogDetailContract , request , context )
29+ if ( ! parsed . success ) return parsed . response
30+
31+ const { id } = parsed . data . params
32+ const { workspaceId } = parsed . data . query
33+
34+ const rows = await db
35+ . select ( {
36+ id : workflowExecutionLogs . id ,
37+ workflowId : workflowExecutionLogs . workflowId ,
38+ executionId : workflowExecutionLogs . executionId ,
39+ deploymentVersionId : workflowExecutionLogs . deploymentVersionId ,
40+ level : workflowExecutionLogs . level ,
41+ status : workflowExecutionLogs . status ,
42+ trigger : workflowExecutionLogs . trigger ,
43+ startedAt : workflowExecutionLogs . startedAt ,
44+ endedAt : workflowExecutionLogs . endedAt ,
45+ totalDurationMs : workflowExecutionLogs . totalDurationMs ,
46+ executionData : workflowExecutionLogs . executionData ,
47+ cost : workflowExecutionLogs . cost ,
48+ files : workflowExecutionLogs . files ,
49+ createdAt : workflowExecutionLogs . createdAt ,
50+ workflowName : workflow . name ,
51+ workflowDescription : workflow . description ,
52+ workflowColor : workflow . color ,
53+ workflowFolderId : workflow . folderId ,
54+ workflowUserId : workflow . userId ,
55+ workflowWorkspaceId : workflow . workspaceId ,
56+ workflowCreatedAt : workflow . createdAt ,
57+ workflowUpdatedAt : workflow . updatedAt ,
58+ deploymentVersion : workflowDeploymentVersion . version ,
59+ deploymentVersionName : workflowDeploymentVersion . name ,
60+ pausedStatus : pausedExecutions . status ,
61+ pausedTotalPauseCount : pausedExecutions . totalPauseCount ,
62+ pausedResumedCount : pausedExecutions . resumedCount ,
63+ } )
64+ . from ( workflowExecutionLogs )
65+ . leftJoin ( workflow , eq ( workflowExecutionLogs . workflowId , workflow . id ) )
66+ . leftJoin (
67+ workflowDeploymentVersion ,
68+ eq ( workflowDeploymentVersion . id , workflowExecutionLogs . deploymentVersionId )
69+ )
70+ . leftJoin (
71+ pausedExecutions ,
72+ eq ( pausedExecutions . executionId , workflowExecutionLogs . executionId )
73+ )
74+ . innerJoin (
75+ permissions ,
76+ and (
77+ eq ( permissions . entityType , 'workspace' ) ,
78+ eq ( permissions . entityId , workflowExecutionLogs . workspaceId ) ,
79+ eq ( permissions . userId , userId )
80+ )
81+ )
82+ . where (
83+ and ( eq ( workflowExecutionLogs . id , id ) , eq ( workflowExecutionLogs . workspaceId , workspaceId ) )
84+ )
85+ . limit ( 1 )
3186
32- const userId = session . user . id
33- const { id } = logIdParamsSchema . parse ( await params )
87+ const log = rows [ 0 ]
3488
35- const rows = await db
89+ if ( ! log ) {
90+ const jobRows = await db
3691 . select ( {
37- id : workflowExecutionLogs . id ,
38- workflowId : workflowExecutionLogs . workflowId ,
39- executionId : workflowExecutionLogs . executionId ,
40- stateSnapshotId : workflowExecutionLogs . stateSnapshotId ,
41- deploymentVersionId : workflowExecutionLogs . deploymentVersionId ,
42- level : workflowExecutionLogs . level ,
43- status : workflowExecutionLogs . status ,
44- trigger : workflowExecutionLogs . trigger ,
45- startedAt : workflowExecutionLogs . startedAt ,
46- endedAt : workflowExecutionLogs . endedAt ,
47- totalDurationMs : workflowExecutionLogs . totalDurationMs ,
48- executionData : workflowExecutionLogs . executionData ,
49- cost : workflowExecutionLogs . cost ,
50- files : workflowExecutionLogs . files ,
51- createdAt : workflowExecutionLogs . createdAt ,
52- workflowName : workflow . name ,
53- workflowDescription : workflow . description ,
54- workflowColor : workflow . color ,
55- workflowFolderId : workflow . folderId ,
56- workflowUserId : workflow . userId ,
57- workflowWorkspaceId : workflow . workspaceId ,
58- workflowCreatedAt : workflow . createdAt ,
59- workflowUpdatedAt : workflow . updatedAt ,
60- deploymentVersion : workflowDeploymentVersion . version ,
61- deploymentVersionName : workflowDeploymentVersion . name ,
92+ id : jobExecutionLogs . id ,
93+ executionId : jobExecutionLogs . executionId ,
94+ level : jobExecutionLogs . level ,
95+ status : jobExecutionLogs . status ,
96+ trigger : jobExecutionLogs . trigger ,
97+ startedAt : jobExecutionLogs . startedAt ,
98+ endedAt : jobExecutionLogs . endedAt ,
99+ totalDurationMs : jobExecutionLogs . totalDurationMs ,
100+ executionData : jobExecutionLogs . executionData ,
101+ cost : jobExecutionLogs . cost ,
102+ createdAt : jobExecutionLogs . createdAt ,
62103 } )
63- . from ( workflowExecutionLogs )
64- . leftJoin ( workflow , eq ( workflowExecutionLogs . workflowId , workflow . id ) )
65- . leftJoin (
66- workflowDeploymentVersion ,
67- eq ( workflowDeploymentVersion . id , workflowExecutionLogs . deploymentVersionId )
68- )
104+ . from ( jobExecutionLogs )
69105 . innerJoin (
70106 permissions ,
71107 and (
72108 eq ( permissions . entityType , 'workspace' ) ,
73- eq ( permissions . entityId , workflowExecutionLogs . workspaceId ) ,
109+ eq ( permissions . entityId , jobExecutionLogs . workspaceId ) ,
74110 eq ( permissions . userId , userId )
75111 )
76112 )
77- . where ( eq ( workflowExecutionLogs . id , id ) )
113+ . where ( and ( eq ( jobExecutionLogs . id , id ) , eq ( jobExecutionLogs . workspaceId , workspaceId ) ) )
78114 . limit ( 1 )
79115
80- const log = rows [ 0 ]
116+ const jobLog = jobRows [ 0 ]
117+ if ( ! jobLog ) {
118+ return NextResponse . json ( { error : 'Not found' } , { status : 404 } )
119+ }
81120
82- // Fallback: check job_execution_logs
83- if ( ! log ) {
84- const jobRows = await db
85- . select ( {
86- id : jobExecutionLogs . id ,
87- executionId : jobExecutionLogs . executionId ,
88- level : jobExecutionLogs . level ,
89- status : jobExecutionLogs . status ,
90- trigger : jobExecutionLogs . trigger ,
91- startedAt : jobExecutionLogs . startedAt ,
92- endedAt : jobExecutionLogs . endedAt ,
93- totalDurationMs : jobExecutionLogs . totalDurationMs ,
94- executionData : jobExecutionLogs . executionData ,
95- cost : jobExecutionLogs . cost ,
96- createdAt : jobExecutionLogs . createdAt ,
97- } )
98- . from ( jobExecutionLogs )
99- . innerJoin (
100- permissions ,
101- and (
102- eq ( permissions . entityType , 'workspace' ) ,
103- eq ( permissions . entityId , jobExecutionLogs . workspaceId ) ,
104- eq ( permissions . userId , userId )
105- )
106- )
107- . where ( eq ( jobExecutionLogs . id , id ) )
108- . limit ( 1 )
121+ const execData = ( jobLog . executionData as Record < string , unknown > | null ) ?? { }
122+ const data = {
123+ id : jobLog . id ,
124+ workflowId : null ,
125+ executionId : jobLog . executionId ,
126+ deploymentVersionId : null ,
127+ deploymentVersion : null ,
128+ deploymentVersionName : null ,
129+ level : jobLog . level ,
130+ status : jobLog . status ,
131+ duration : jobLog . totalDurationMs ? `${ jobLog . totalDurationMs } ms` : null ,
132+ trigger : jobLog . trigger ,
133+ createdAt : jobLog . startedAt . toISOString ( ) ,
134+ workflow : null ,
135+ jobTitle :
136+ ( ( execData . trigger as Record < string , unknown > | undefined ) ?. source as string ) ?? null ,
137+ cost : jobLog . cost ?? null ,
138+ pauseSummary : { status : null , total : 0 , resumed : 0 } ,
139+ hasPendingPause : false ,
140+ executionData : {
141+ totalDuration : jobLog . totalDurationMs ,
142+ enhanced : true as const ,
143+ ...execData ,
144+ } ,
145+ files : null ,
146+ }
109147
110- const jobLog = jobRows [ 0 ]
111- if ( ! jobLog ) {
112- return NextResponse . json ( { error : 'Not found' } , { status : 404 } )
113- }
148+ return NextResponse . json ( { data } )
149+ }
114150
115- const execData = jobLog . executionData as Record < string , any > | null
116- const response = {
117- id : jobLog . id ,
118- workflowId : null ,
119- executionId : jobLog . executionId ,
120- deploymentVersionId : null ,
121- deploymentVersion : null ,
122- deploymentVersionName : null ,
123- level : jobLog . level ,
124- status : jobLog . status ,
125- duration : jobLog . totalDurationMs ? `${ jobLog . totalDurationMs } ms` : null ,
126- trigger : jobLog . trigger ,
127- createdAt : jobLog . startedAt . toISOString ( ) ,
128- workflow : null ,
129- jobTitle : ( execData ?. trigger ?. source as string ) || null ,
130- executionData : {
131- totalDuration : jobLog . totalDurationMs ,
132- ...execData ,
133- enhanced : true ,
134- } ,
135- cost : jobLog . cost as any ,
151+ const workflowSummary = log . workflowId
152+ ? {
153+ id : log . workflowId ,
154+ name : log . workflowName ,
155+ description : log . workflowDescription ,
156+ color : log . workflowColor ,
157+ folderId : log . workflowFolderId ,
158+ userId : log . workflowUserId ,
159+ workspaceId : log . workflowWorkspaceId ,
160+ createdAt : log . workflowCreatedAt ?. toISOString ( ) ?? null ,
161+ updatedAt : log . workflowUpdatedAt ?. toISOString ( ) ?? null ,
136162 }
163+ : null
137164
138- return NextResponse . json ( { data : response } )
139- }
165+ const totalPauseCount = Number ( log . pausedTotalPauseCount ?? 0 )
166+ const resumedCount = Number ( log . pausedResumedCount ?? 0 )
167+ const hasPendingPause =
168+ ( totalPauseCount > 0 && resumedCount < totalPauseCount ) ||
169+ ( log . pausedStatus !== null && log . pausedStatus !== 'fully_resumed' )
140170
141- const workflowSummary = log . workflowId
142- ? {
143- id : log . workflowId ,
144- name : log . workflowName ,
145- description : log . workflowDescription ,
146- color : log . workflowColor ,
147- folderId : log . workflowFolderId ,
148- userId : log . workflowUserId ,
149- workspaceId : log . workflowWorkspaceId ,
150- createdAt : log . workflowCreatedAt ,
151- updatedAt : log . workflowUpdatedAt ,
152- }
153- : null
171+ const data = {
172+ id : log . id ,
173+ workflowId : log . workflowId ,
174+ executionId : log . executionId ,
175+ deploymentVersionId : log . deploymentVersionId ,
176+ deploymentVersion : log . deploymentVersion ?? null ,
177+ deploymentVersionName : log . deploymentVersionName ?? null ,
178+ level : log . level ,
179+ status : log . status ,
180+ duration : log . totalDurationMs ? `${ log . totalDurationMs } ms` : null ,
181+ trigger : log . trigger ,
182+ createdAt : log . startedAt . toISOString ( ) ,
183+ workflow : workflowSummary ,
184+ jobTitle : null ,
185+ cost : log . cost ?? null ,
186+ pauseSummary : {
187+ status : log . pausedStatus ?? null ,
188+ total : totalPauseCount ,
189+ resumed : resumedCount ,
190+ } ,
191+ hasPendingPause,
192+ executionData : {
193+ totalDuration : log . totalDurationMs ,
194+ enhanced : true as const ,
195+ ...( ( log . executionData as Record < string , unknown > | null ) ?? { } ) ,
196+ } ,
197+ files : log . files ?? null ,
198+ }
154199
155- const response = {
156- id : log . id ,
157- workflowId : log . workflowId ,
158- executionId : log . executionId ,
159- deploymentVersionId : log . deploymentVersionId ,
160- deploymentVersion : log . deploymentVersion ?? null ,
161- deploymentVersionName : log . deploymentVersionName ?? null ,
162- level : log . level ,
163- status : log . status ,
164- duration : log . totalDurationMs ? `${ log . totalDurationMs } ms` : null ,
165- trigger : log . trigger ,
166- createdAt : log . startedAt . toISOString ( ) ,
167- files : log . files || undefined ,
168- workflow : workflowSummary ,
169- executionData : {
170- totalDuration : log . totalDurationMs ,
171- ...( log . executionData as any ) ,
172- enhanced : true ,
173- } ,
174- cost : log . cost as any ,
175- }
200+ logger . debug ( 'Fetched log detail' , { id, workspaceId } )
176201
177- return NextResponse . json ( { data : response } )
178- } catch ( error : any ) {
179- logger . error ( `[${ requestId } ] log details fetch error` , error )
180- return NextResponse . json ( { error : error . message } , { status : 500 } )
181- }
202+ return NextResponse . json ( { data } )
182203 }
183204)
0 commit comments