Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
219a065
v0.4.12: guardrails, mistral models, privacy policy updates (#1608)
waleedlatif1 Oct 12, 2025
7f82ed3
v0.4.13: bugfixes for dev containers, posthog redirect, helm updates
icecrasher321 Oct 13, 2025
fb0fa1f
v0.4.14: canvas speedup and copilot context window
Sg312 Oct 14, 2025
2bc8c7b
v0.4.15: helm chart updates, telegram tools, youtube tools, file uplo…
waleedlatif1 Oct 15, 2025
04f109c
v0.4.16: executions dashboard, UI fixes, zep tools, slack fixes
icecrasher321 Oct 16, 2025
da091df
v0.4.17: input format + files support for webhooks, docs updates, das…
waleedlatif1 Oct 16, 2025
e4ddeb0
v0.4.18: file upload tools, copilot upgrade, docs changes, model filt…
icecrasher321 Oct 19, 2025
641e353
v0.4.19: landing page fix
icecrasher321 Oct 19, 2025
9751c9f
v0.4.20: internal request, kb url fixes, docs styling
icecrasher321 Oct 21, 2025
1b7437a
v0.4.21: more internal auth changes, supabase vector search tool
icecrasher321 Oct 22, 2025
71ae27b
v0.4.22: fix execution context pass for google sheets
icecrasher321 Oct 22, 2025
9b2490c
v0.4.23: webflow tools + triggers, copilot api key fix (#1723)
waleedlatif1 Oct 23, 2025
7f1ff7f
fix(billing): should allow restoring subscription (#1728)
icecrasher321 Oct 25, 2025
a02016e
v0.4.24: sso for chat deployment, usage indicator for file storage, m…
icecrasher321 Oct 27, 2025
9a4b9e2
v0.4.25: variables block, sort ordering for kb, careers page, storage…
waleedlatif1 Oct 29, 2025
038f087
improvement(api-keys): move to workspace level
icecrasher321 Oct 30, 2025
f0816cb
remove migration to prep merge
icecrasher321 Oct 30, 2025
37bfaf0
Merge branch 'staging', remote-tracking branch 'origin' into fix/work…
icecrasher321 Oct 30, 2025
f8421f4
remove two more unused cols
icecrasher321 Oct 30, 2025
018ed2c
prep staging merge
icecrasher321 Oct 30, 2025
e663322
Merge branch 'staging' into fix/workspace-api-keys
icecrasher321 Oct 30, 2025
338be23
add migration back
icecrasher321 Oct 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/docs/content/docs/de/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/de/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/en/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/en/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/es/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/es/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/fr/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/fr/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/ja/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/ja/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/zh/sdks/python.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,6 @@ class AsyncExecutionResult:
class WorkflowStatus:
is_deployed: bool
deployed_at: Optional[str] = None
is_published: bool = False
needs_redeployment: bool = False
```

Expand Down
1 change: 0 additions & 1 deletion apps/docs/content/docs/zh/sdks/typescript.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ interface AsyncExecutionResult {
interface WorkflowStatus {
isDeployed: boolean;
deployedAt?: string;
isPublished: boolean;
needsRedeployment: boolean;
}
```
Expand Down
7 changes: 5 additions & 2 deletions apps/sim/app/api/billing/portal/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db } from '@sim/db'
import { subscription as subscriptionTable, user } from '@sim/db/schema'
import { and, eq } from 'drizzle-orm'
import { and, eq, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { requireStripeClient } from '@/lib/billing/stripe-client'
Expand Down Expand Up @@ -38,7 +38,10 @@ export async function POST(request: NextRequest) {
.where(
and(
eq(subscriptionTable.referenceId, organizationId),
eq(subscriptionTable.status, 'active')
or(
eq(subscriptionTable.status, 'active'),
eq(subscriptionTable.cancelAtPeriodEnd, true)
)
)
)
.limit(1)
Expand Down
22 changes: 11 additions & 11 deletions apps/sim/app/api/webhooks/trigger/[path]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,17 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

vi.doMock('@/lib/api-key/service', async () => {
const actual = await vi.importActual('@/lib/api-key/service')
vi.doMock('@/lib/workspaces/utils', async () => {
const actual = await vi.importActual('@/lib/workspaces/utils')
return {
...(actual as Record<string, unknown>),
getApiKeyOwnerUserId: vi
getWorkspaceBilledAccountUserId: vi
.fn()
.mockImplementation(async (pinnedApiKeyId: string | null | undefined) =>
pinnedApiKeyId ? 'test-user-id' : null
.mockImplementation(async (workspaceId: string | null | undefined) =>
workspaceId ? 'test-user-id' : null
),
}
})
Expand Down Expand Up @@ -240,7 +240,7 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

const req = createMockRequest('POST', { event: 'test', id: 'test-123' })
Expand Down Expand Up @@ -272,7 +272,7 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

const headers = {
Expand Down Expand Up @@ -307,7 +307,7 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

const headers = {
Expand Down Expand Up @@ -338,7 +338,7 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

vi.doMock('@trigger.dev/sdk', () => ({
Expand Down Expand Up @@ -388,7 +388,7 @@ describe('Webhook Trigger API Route', () => {
globalMockData.workflows.push({
id: 'test-workflow-id',
userId: 'test-user-id',
pinnedApiKeyId: 'test-pinned-api-key-id',
workspaceId: 'test-workspace-id',
})

vi.doMock('@trigger.dev/sdk', () => ({
Expand Down
149 changes: 7 additions & 142 deletions apps/sim/app/api/workflows/[id]/deploy/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { apiKey, db, workflow, workflowDeploymentVersion } from '@sim/db'
import { db, workflow, workflowDeploymentVersion } from '@sim/db'
import { and, desc, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import { deployWorkflow } from '@/lib/workflows/db-helpers'
Expand Down Expand Up @@ -38,35 +38,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
})
}

let keyInfo: { name: string; type: 'personal' | 'workspace' } | null = null

if (workflowData.pinnedApiKeyId) {
const pinnedKey = await db
.select({ key: apiKey.key, name: apiKey.name, type: apiKey.type })
.from(apiKey)
.where(eq(apiKey.id, workflowData.pinnedApiKeyId))
.limit(1)

if (pinnedKey.length > 0) {
keyInfo = { name: pinnedKey[0].name, type: pinnedKey[0].type as 'personal' | 'workspace' }
}
} else {
const userApiKey = await db
.select({
key: apiKey.key,
name: apiKey.name,
type: apiKey.type,
})
.from(apiKey)
.where(and(eq(apiKey.userId, workflowData.userId), eq(apiKey.type, 'personal')))
.orderBy(desc(apiKey.lastUsed), desc(apiKey.createdAt))
.limit(1)

if (userApiKey.length > 0) {
keyInfo = { name: userApiKey[0].name, type: userApiKey[0].type as 'personal' | 'workspace' }
}
}

let needsRedeployment = false
const [active] = await db
.select({ state: workflowDeploymentVersion.state })
Expand Down Expand Up @@ -97,7 +68,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{

logger.info(`[${requestId}] Successfully retrieved deployment info: ${id}`)

const responseApiKeyInfo = keyInfo ? `${keyInfo.name} (${keyInfo.type})` : 'No API key found'
const responseApiKeyInfo = workflowData.workspaceId ? 'Workspace API keys' : 'Personal API keys'

return createSuccessResponse({
apiKey: responseApiKeyInfo,
Expand Down Expand Up @@ -127,101 +98,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return createErrorResponse(error.message, error.status)
}

const userId = workflowData!.userId

let providedApiKey: string | null = null
try {
const parsed = await request.json()
if (parsed && typeof parsed.apiKey === 'string' && parsed.apiKey.trim().length > 0) {
providedApiKey = parsed.apiKey.trim()
}
} catch (_err) {}

logger.debug(`[${requestId}] Validating API key for deployment`)

let keyInfo: { name: string; type: 'personal' | 'workspace' } | null = null
let matchedKey: {
id: string
key: string
name: string
type: 'personal' | 'workspace'
} | null = null

// Use provided API key, or fall back to existing pinned API key for redeployment
const apiKeyToUse = providedApiKey || workflowData!.pinnedApiKeyId

if (!apiKeyToUse) {
return NextResponse.json(
{ error: 'API key is required. Please create or select an API key before deploying.' },
{ status: 400 }
)
}

let isValidKey = false

const currentUserId = session?.user?.id

if (currentUserId) {
const [personalKey] = await db
.select({
id: apiKey.id,
key: apiKey.key,
name: apiKey.name,
expiresAt: apiKey.expiresAt,
})
.from(apiKey)
.where(
and(
eq(apiKey.id, apiKeyToUse),
eq(apiKey.userId, currentUserId),
eq(apiKey.type, 'personal')
)
)
.limit(1)

if (personalKey) {
if (!personalKey.expiresAt || personalKey.expiresAt >= new Date()) {
matchedKey = { ...personalKey, type: 'personal' }
isValidKey = true
keyInfo = { name: personalKey.name, type: 'personal' }
}
}
}

if (!isValidKey) {
if (workflowData!.workspaceId) {
const [workspaceKey] = await db
.select({
id: apiKey.id,
key: apiKey.key,
name: apiKey.name,
expiresAt: apiKey.expiresAt,
})
.from(apiKey)
.where(
and(
eq(apiKey.id, apiKeyToUse),
eq(apiKey.workspaceId, workflowData!.workspaceId),
eq(apiKey.type, 'workspace')
)
)
.limit(1)

if (workspaceKey) {
if (!workspaceKey.expiresAt || workspaceKey.expiresAt >= new Date()) {
matchedKey = { ...workspaceKey, type: 'workspace' }
isValidKey = true
keyInfo = { name: workspaceKey.name, type: 'workspace' }
}
}
}
}

if (!isValidKey) {
logger.warn(`[${requestId}] Invalid API key ID provided for workflow deployment: ${id}`)
return createErrorResponse('Invalid API key provided', 400)
}

// Attribution: this route is UI-only; require session user as actor
const actorUserId: string | null = session?.user?.id ?? null
if (!actorUserId) {
Expand All @@ -232,8 +108,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
const deployResult = await deployWorkflow({
workflowId: id,
deployedBy: actorUserId,
pinnedApiKeyId: matchedKey?.id,
includeDeployedState: true,
workflowName: workflowData!.name,
})

Expand All @@ -243,20 +117,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{

const deployedAt = deployResult.deployedAt!

if (matchedKey) {
try {
await db
.update(apiKey)
.set({ lastUsed: new Date(), updatedAt: new Date() })
.where(eq(apiKey.id, matchedKey.id))
} catch (e) {
logger.warn(`[${requestId}] Failed to update lastUsed for api key`)
}
}

logger.info(`[${requestId}] Workflow deployed successfully: ${id}`)

const responseApiKeyInfo = keyInfo ? `${keyInfo.name} (${keyInfo.type})` : 'Default key'
const responseApiKeyInfo = workflowData!.workspaceId
? 'Workspace API keys'
: 'Personal API keys'

return createSuccessResponse({
apiKey: responseApiKeyInfo,
Expand Down Expand Up @@ -298,7 +163,7 @@ export async function DELETE(

await tx
.update(workflow)
.set({ isDeployed: false, deployedAt: null, deployedState: null, pinnedApiKeyId: null })
.set({ isDeployed: false, deployedAt: null })
.where(eq(workflow.id, id))
})

Expand Down
Loading