Skip to content

Commit ba19653

Browse files
committed
Merge staging into feat/ee
2 parents 22cfa0a + a9b7d75 commit ba19653

File tree

18 files changed

+350
-190
lines changed

18 files changed

+350
-190
lines changed

apps/sim/app/api/cron/cleanup-stale-executions/route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { verifyCronAuth } from '@/lib/auth/internal'
88
const logger = createLogger('CleanupStaleExecutions')
99

1010
const STALE_THRESHOLD_MINUTES = 30
11+
const MAX_INT32 = 2_147_483_647
1112

1213
export async function GET(request: NextRequest) {
1314
try {
@@ -45,13 +46,14 @@ export async function GET(request: NextRequest) {
4546
try {
4647
const staleDurationMs = Date.now() - new Date(execution.startedAt).getTime()
4748
const staleDurationMinutes = Math.round(staleDurationMs / 60000)
49+
const totalDurationMs = Math.min(staleDurationMs, MAX_INT32)
4850

4951
await db
5052
.update(workflowExecutionLogs)
5153
.set({
5254
status: 'failed',
5355
endedAt: new Date(),
54-
totalDurationMs: staleDurationMs,
56+
totalDurationMs,
5557
executionData: sql`jsonb_set(
5658
COALESCE(execution_data, '{}'::jsonb),
5759
ARRAY['error'],

apps/sim/app/api/mcp/serve/[serverId]/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ async function handleToolsCall(
284284
content: [
285285
{ type: 'text', text: JSON.stringify(executeResult.output || executeResult, null, 2) },
286286
],
287-
isError: !executeResult.success,
287+
isError: executeResult.success === false,
288288
}
289289

290290
return NextResponse.json(createResponse(id, result))

apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { z } from 'zod'
2020
import { getEmailSubject, renderInvitationEmail } from '@/components/emails'
2121
import { getSession } from '@/lib/auth'
2222
import { hasAccessControlAccess } from '@/lib/billing'
23+
import { syncUsageLimitsFromSubscription } from '@/lib/billing/core/usage'
2324
import { requireStripeClient } from '@/lib/billing/stripe-client'
2425
import { getBaseUrl } from '@/lib/core/utils/urls'
2526
import { sendEmail } from '@/lib/messaging/email/mailer'
@@ -501,6 +502,18 @@ export async function PUT(
501502
}
502503
}
503504

505+
if (status === 'accepted') {
506+
try {
507+
await syncUsageLimitsFromSubscription(session.user.id)
508+
} catch (syncError) {
509+
logger.error('Failed to sync usage limits after joining org', {
510+
userId: session.user.id,
511+
organizationId,
512+
error: syncError,
513+
})
514+
}
515+
}
516+
504517
logger.info(`Organization invitation ${status}`, {
505518
organizationId,
506519
invitationId,

apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { and, eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { z } from 'zod'
77
import { getSession } from '@/lib/auth'
8+
import { hasActiveSubscription } from '@/lib/billing'
89

910
const logger = createLogger('SubscriptionTransferAPI')
1011

@@ -88,6 +89,14 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
8889
)
8990
}
9091

92+
// Check if org already has an active subscription (prevent duplicates)
93+
if (await hasActiveSubscription(organizationId)) {
94+
return NextResponse.json(
95+
{ error: 'Organization already has an active subscription' },
96+
{ status: 409 }
97+
)
98+
}
99+
91100
await db
92101
.update(subscription)
93102
.set({ referenceId: organizationId })

apps/sim/app/api/v1/admin/users/[id]/billing/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ export const PATCH = withAdminAuthParams<RouteParams>(async (request, context) =
203203
}
204204

205205
updateData.billingBlocked = body.billingBlocked
206+
// Clear the reason when unblocking
207+
if (body.billingBlocked === false) {
208+
updateData.billingBlockedReason = null
209+
}
206210
updated.push('billingBlocked')
207211
}
208212

apps/sim/app/api/workflows/[id]/execute-from-block/route.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
1-
import { db, workflow as workflowTable } from '@sim/db'
21
import { createLogger } from '@sim/logger'
3-
import { eq } from 'drizzle-orm'
42
import { type NextRequest, NextResponse } from 'next/server'
53
import { v4 as uuidv4 } from 'uuid'
64
import { z } from 'zod'
75
import { checkHybridAuth } from '@/lib/auth/hybrid'
86
import { generateRequestId } from '@/lib/core/utils/request'
97
import { SSE_HEADERS } from '@/lib/core/utils/sse'
108
import { markExecutionCancelled } from '@/lib/execution/cancellation'
9+
import { preprocessExecution } from '@/lib/execution/preprocessing'
1110
import { LoggingSession } from '@/lib/logs/execution/logging-session'
1211
import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core'
1312
import { createSSECallbacks } from '@/lib/workflows/executor/execution-events'
@@ -75,12 +74,31 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
7574
const { startBlockId, sourceSnapshot, input } = validation.data
7675
const executionId = uuidv4()
7776

78-
const [workflowRecord] = await db
79-
.select({ workspaceId: workflowTable.workspaceId, userId: workflowTable.userId })
80-
.from(workflowTable)
81-
.where(eq(workflowTable.id, workflowId))
82-
.limit(1)
77+
// Run preprocessing checks (billing, rate limits, usage limits)
78+
const preprocessResult = await preprocessExecution({
79+
workflowId,
80+
userId,
81+
triggerType: 'manual',
82+
executionId,
83+
requestId,
84+
checkRateLimit: false, // Manual executions don't rate limit
85+
checkDeployment: false, // Run-from-block doesn't require deployment
86+
})
87+
88+
if (!preprocessResult.success) {
89+
const { error } = preprocessResult
90+
logger.warn(`[${requestId}] Preprocessing failed for run-from-block`, {
91+
workflowId,
92+
error: error?.message,
93+
statusCode: error?.statusCode,
94+
})
95+
return NextResponse.json(
96+
{ error: error?.message || 'Execution blocked' },
97+
{ status: error?.statusCode || 500 }
98+
)
99+
}
83100

101+
const workflowRecord = preprocessResult.workflowRecord
84102
if (!workflowRecord?.workspaceId) {
85103
return NextResponse.json({ error: 'Workflow not found or has no workspace' }, { status: 404 })
86104
}
@@ -92,6 +110,7 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
92110
workflowId,
93111
startBlockId,
94112
executedBlocksCount: sourceSnapshot.executedBlocks.length,
113+
billingActorUserId: preprocessResult.actorUserId,
95114
})
96115

97116
const loggingSession = new LoggingSession(workflowId, executionId, 'manual', requestId)

0 commit comments

Comments
 (0)