Skip to content

Commit ff938c7

Browse files
committed
refactor(logs): address PR review feedback
- Whitelist sort columns against logSortBy enum to prevent client crash when non-sortable headers (workflow, trigger) reach the contract parser. - Extract fetchLogDetail helper shared by /api/logs/[id] and /api/logs/by-execution/[executionId] — collapses ~360 duplicated lines to a single source of truth keyed on lookup column.
1 parent 8d86023 commit ff938c7

4 files changed

Lines changed: 218 additions & 361 deletions

File tree

Lines changed: 8 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,10 @@
1-
import { db } from '@sim/db'
2-
import {
3-
jobExecutionLogs,
4-
pausedExecutions,
5-
permissions,
6-
workflow,
7-
workflowDeploymentVersion,
8-
workflowExecutionLogs,
9-
} from '@sim/db/schema'
101
import { createLogger } from '@sim/logger'
11-
import { and, eq } from 'drizzle-orm'
122
import { type NextRequest, NextResponse } from 'next/server'
133
import { getLogDetailContract } from '@/lib/api/contracts/logs'
144
import { parseRequest } from '@/lib/api/server'
155
import { getSession } from '@/lib/auth'
166
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
7+
import { fetchLogDetail } from '@/lib/logs/fetch-log-detail'
178

189
const logger = createLogger('LogDetailsByIdAPI')
1910

@@ -24,181 +15,22 @@ export const GET = withRouteHandler(
2415
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2516
}
2617

27-
const userId = session.user.id
2818
const parsed = await parseRequest(getLogDetailContract, request, context)
2919
if (!parsed.success) return parsed.response
3020

3121
const { id } = parsed.data.params
3222
const { workspaceId } = parsed.data.query
3323

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)
24+
const data = await fetchLogDetail({
25+
userId: session.user.id,
26+
workspaceId,
27+
lookupColumn: 'id',
28+
lookupValue: id,
29+
})
8630

87-
const log = rows[0]
88-
89-
if (!log) {
90-
const jobRows = await db
91-
.select({
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,
103-
})
104-
.from(jobExecutionLogs)
105-
.innerJoin(
106-
permissions,
107-
and(
108-
eq(permissions.entityType, 'workspace'),
109-
eq(permissions.entityId, jobExecutionLogs.workspaceId),
110-
eq(permissions.userId, userId)
111-
)
112-
)
113-
.where(and(eq(jobExecutionLogs.id, id), eq(jobExecutionLogs.workspaceId, workspaceId)))
114-
.limit(1)
115-
116-
const jobLog = jobRows[0]
117-
if (!jobLog) {
118-
return NextResponse.json({ error: 'Not found' }, { status: 404 })
119-
}
120-
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-
...execData,
143-
enhanced: true as const,
144-
},
145-
files: null,
146-
}
147-
148-
return NextResponse.json({ data })
149-
}
150-
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,
162-
}
163-
: null
164-
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')
170-
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-
...((log.executionData as Record<string, unknown> | null) ?? {}),
195-
enhanced: true as const,
196-
},
197-
files: log.files ?? null,
198-
}
31+
if (!data) return NextResponse.json({ error: 'Not found' }, { status: 404 })
19932

20033
logger.debug('Fetched log detail', { id, workspaceId })
201-
20234
return NextResponse.json({ data })
20335
}
20436
)

0 commit comments

Comments
 (0)