Skip to content

Commit 8e6ea11

Browse files
committed
fix(webhooks/schedules): deployment version friendly
1 parent d113175 commit 8e6ea11

File tree

20 files changed

+10967
-88
lines changed

20 files changed

+10967
-88
lines changed

apps/sim/app/api/schedules/execute/route.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { db, workflowSchedule } from '@sim/db'
1+
import { db, workflowDeploymentVersion, workflowSchedule } from '@sim/db'
22
import { createLogger } from '@sim/logger'
33
import { tasks } from '@trigger.dev/sdk'
4-
import { and, eq, isNull, lt, lte, not, or } from 'drizzle-orm'
4+
import { and, eq, isNull, lt, lte, not, or, sql } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { verifyCronAuth } from '@/lib/auth/internal'
77
import { isTriggerDevEnabled } from '@/lib/core/config/feature-flags'
@@ -37,7 +37,8 @@ export async function GET(request: NextRequest) {
3737
or(
3838
isNull(workflowSchedule.lastQueuedAt),
3939
lt(workflowSchedule.lastQueuedAt, workflowSchedule.nextRunAt)
40-
)
40+
),
41+
sql`${workflowSchedule.deploymentVersionId} = (select ${workflowDeploymentVersion.id} from ${workflowDeploymentVersion} where ${workflowDeploymentVersion.workflowId} = ${workflowSchedule.workflowId} and ${workflowDeploymentVersion.isActive} = true)`
4142
)
4243
)
4344
.returning({

apps/sim/app/api/schedules/route.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
2-
import { workflow, workflowSchedule } from '@sim/db/schema'
2+
import { workflow, workflowDeploymentVersion, workflowSchedule } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { and, eq } from 'drizzle-orm'
4+
import { and, eq, isNull, or } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { getSession } from '@/lib/auth'
77
import { generateRequestId } from '@/lib/core/utils/request'
@@ -62,9 +62,24 @@ export async function GET(req: NextRequest) {
6262
}
6363

6464
const schedule = await db
65-
.select()
65+
.select({ schedule: workflowSchedule })
6666
.from(workflowSchedule)
67-
.where(conditions.length > 1 ? and(...conditions) : conditions[0])
67+
.leftJoin(
68+
workflowDeploymentVersion,
69+
and(
70+
eq(workflowDeploymentVersion.workflowId, workflowSchedule.workflowId),
71+
eq(workflowDeploymentVersion.isActive, true)
72+
)
73+
)
74+
.where(
75+
and(
76+
...conditions,
77+
or(
78+
eq(workflowSchedule.deploymentVersionId, workflowDeploymentVersion.id),
79+
and(isNull(workflowDeploymentVersion.id), isNull(workflowSchedule.deploymentVersionId))
80+
)
81+
)
82+
)
6883
.limit(1)
6984

7085
const headers = new Headers()
@@ -74,7 +89,7 @@ export async function GET(req: NextRequest) {
7489
return NextResponse.json({ schedule: null }, { headers })
7590
}
7691

77-
const scheduleData = schedule[0]
92+
const scheduleData = schedule[0].schedule
7893
const isDisabled = scheduleData.status === 'disabled'
7994
const hasFailures = scheduleData.failedCount > 0
8095

apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,17 @@ export const POST = withAdminAuthParams<RouteParams>(async (request, context) =>
6060
return internalErrorResponse(deployResult.error || 'Failed to deploy workflow')
6161
}
6262

63-
const scheduleResult = await createSchedulesForDeploy(workflowId, normalizedData.blocks, db)
63+
if (!deployResult.deploymentVersionId) {
64+
await undeployWorkflow({ workflowId })
65+
return internalErrorResponse('Failed to resolve deployment version')
66+
}
67+
68+
const scheduleResult = await createSchedulesForDeploy(
69+
workflowId,
70+
normalizedData.blocks,
71+
db,
72+
deployResult.deploymentVersionId
73+
)
6474
if (!scheduleResult.success) {
6575
logger.warn(`Schedule creation failed for workflow ${workflowId}: ${scheduleResult.error}`)
6676
}

apps/sim/app/api/webhooks/route.ts

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from '@sim/db'
2-
import { webhook, workflow } from '@sim/db/schema'
2+
import { webhook, workflow, workflowDeploymentVersion } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { and, desc, eq } from 'drizzle-orm'
4+
import { and, desc, eq, isNull, or } from 'drizzle-orm'
55
import { nanoid } from 'nanoid'
66
import { type NextRequest, NextResponse } from 'next/server'
77
import { getSession } from '@/lib/auth'
@@ -71,7 +71,23 @@ export async function GET(request: NextRequest) {
7171
})
7272
.from(webhook)
7373
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
74-
.where(and(eq(webhook.workflowId, workflowId), eq(webhook.blockId, blockId)))
74+
.leftJoin(
75+
workflowDeploymentVersion,
76+
and(
77+
eq(workflowDeploymentVersion.workflowId, workflow.id),
78+
eq(workflowDeploymentVersion.isActive, true)
79+
)
80+
)
81+
.where(
82+
and(
83+
eq(webhook.workflowId, workflowId),
84+
eq(webhook.blockId, blockId),
85+
or(
86+
eq(webhook.deploymentVersionId, workflowDeploymentVersion.id),
87+
and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId))
88+
)
89+
)
90+
)
7591
.orderBy(desc(webhook.updatedAt))
7692

7793
logger.info(
@@ -149,7 +165,23 @@ export async function POST(request: NextRequest) {
149165
const existingForBlock = await db
150166
.select({ id: webhook.id, path: webhook.path })
151167
.from(webhook)
152-
.where(and(eq(webhook.workflowId, workflowId), eq(webhook.blockId, blockId)))
168+
.leftJoin(
169+
workflowDeploymentVersion,
170+
and(
171+
eq(workflowDeploymentVersion.workflowId, workflowId),
172+
eq(workflowDeploymentVersion.isActive, true)
173+
)
174+
)
175+
.where(
176+
and(
177+
eq(webhook.workflowId, workflowId),
178+
eq(webhook.blockId, blockId),
179+
or(
180+
eq(webhook.deploymentVersionId, workflowDeploymentVersion.id),
181+
and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId))
182+
)
183+
)
184+
)
153185
.limit(1)
154186

155187
if (existingForBlock.length > 0) {
@@ -225,7 +257,23 @@ export async function POST(request: NextRequest) {
225257
const existingForBlock = await db
226258
.select({ id: webhook.id })
227259
.from(webhook)
228-
.where(and(eq(webhook.workflowId, workflowId), eq(webhook.blockId, blockId)))
260+
.leftJoin(
261+
workflowDeploymentVersion,
262+
and(
263+
eq(workflowDeploymentVersion.workflowId, workflowId),
264+
eq(workflowDeploymentVersion.isActive, true)
265+
)
266+
)
267+
.where(
268+
and(
269+
eq(webhook.workflowId, workflowId),
270+
eq(webhook.blockId, blockId),
271+
or(
272+
eq(webhook.deploymentVersionId, workflowDeploymentVersion.id),
273+
and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId))
274+
)
275+
)
276+
)
229277
.limit(1)
230278
if (existingForBlock.length > 0) {
231279
targetWebhookId = existingForBlock[0].id

apps/sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 47 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import {
1010
loadWorkflowFromNormalizedTables,
1111
undeployWorkflow,
1212
} from '@/lib/workflows/persistence/utils'
13-
import { createSchedulesForDeploy, validateWorkflowSchedules } from '@/lib/workflows/schedules'
13+
import {
14+
cleanupDeploymentVersion,
15+
createSchedulesForDeploy,
16+
validateWorkflowSchedules,
17+
} from '@/lib/workflows/schedules'
1418
import { validateWorkflowPermissions } from '@/lib/workflows/utils'
1519
import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils'
1620

@@ -131,41 +135,69 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
131135
return createErrorResponse(`Invalid schedule configuration: ${scheduleValidation.error}`, 400)
132136
}
133137

138+
const deployResult = await deployWorkflow({
139+
workflowId: id,
140+
deployedBy: actorUserId,
141+
workflowName: workflowData!.name,
142+
})
143+
144+
if (!deployResult.success) {
145+
return createErrorResponse(deployResult.error || 'Failed to deploy workflow', 500)
146+
}
147+
148+
const deployedAt = deployResult.deployedAt!
149+
const deploymentVersionId = deployResult.deploymentVersionId
150+
151+
if (!deploymentVersionId) {
152+
await undeployWorkflow({ workflowId: id })
153+
return createErrorResponse('Failed to resolve deployment version', 500)
154+
}
155+
134156
const triggerSaveResult = await saveTriggerWebhooksForDeploy({
135157
request,
136158
workflowId: id,
137159
workflow: workflowData,
138160
userId: actorUserId,
139161
blocks: normalizedData.blocks,
140162
requestId,
163+
deploymentVersionId,
141164
})
142165

143166
if (!triggerSaveResult.success) {
167+
await cleanupDeploymentVersion({
168+
workflowId: id,
169+
workflow: workflowData as Record<string, unknown>,
170+
requestId,
171+
deploymentVersionId,
172+
})
173+
await undeployWorkflow({ workflowId: id })
144174
return createErrorResponse(
145175
triggerSaveResult.error?.message || 'Failed to save trigger configuration',
146176
triggerSaveResult.error?.status || 500
147177
)
148178
}
149179

150-
const deployResult = await deployWorkflow({
151-
workflowId: id,
152-
deployedBy: actorUserId,
153-
workflowName: workflowData!.name,
154-
})
155-
156-
if (!deployResult.success) {
157-
return createErrorResponse(deployResult.error || 'Failed to deploy workflow', 500)
158-
}
159-
160-
const deployedAt = deployResult.deployedAt!
161-
162180
let scheduleInfo: { scheduleId?: string; cronExpression?: string; nextRunAt?: Date } = {}
163-
const scheduleResult = await createSchedulesForDeploy(id, normalizedData.blocks, db)
181+
const scheduleResult = await createSchedulesForDeploy(
182+
id,
183+
normalizedData.blocks,
184+
db,
185+
deploymentVersionId
186+
)
164187
if (!scheduleResult.success) {
165188
logger.error(
166189
`[${requestId}] Failed to create schedule for workflow ${id}: ${scheduleResult.error}`
167190
)
168-
} else if (scheduleResult.scheduleId) {
191+
await cleanupDeploymentVersion({
192+
workflowId: id,
193+
workflow: workflowData as Record<string, unknown>,
194+
requestId,
195+
deploymentVersionId,
196+
})
197+
await undeployWorkflow({ workflowId: id })
198+
return createErrorResponse(scheduleResult.error || 'Failed to create schedule', 500)
199+
}
200+
if (scheduleResult.scheduleId) {
169201
scheduleInfo = {
170202
scheduleId: scheduleResult.scheduleId,
171203
cronExpression: scheduleResult.cronExpression,

0 commit comments

Comments
 (0)