Skip to content

Commit 22c3de1

Browse files
authored
v0.9.18: db, search improvements
2 parents c2d446f + 3684920 commit 22c3de1

89 files changed

Lines changed: 66632 additions & 672 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/docs/content/docs/platform/enterprise/whitelabeling.mdx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ Go to **Settings → Organization → White-labeling** in your workspace.
2323
| Field | Description |
2424
|-------|-------------|
2525
| **Logo** | Shown in the collapsed sidebar. Square image (PNG, JPEG, SVG, or WebP). Max 5 MB. |
26-
| **Wordmark** | Shown in the expanded sidebar. Wide image (PNG, JPEG, SVG, or WebP). Max 5 MB. |
26+
| **Wordmark** | Shown in the expanded sidebar and application loading screens. Wide image (PNG, JPEG, SVG, or WebP). Max 5 MB. |
2727
| **Brand name** | Replaces "Sim" in the sidebar and select UI elements. Max 64 characters. |
2828

29+
Loading screens use the wordmark, never the square logo. Without a wordmark, they show the brand name; when the name is still Sim, they use the built-in Sim wordmark.
2930

3031
### 3. Configure colors
3132

‎apps/realtime/src/database/operations.ts‎

Lines changed: 99 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ import {
3838
normalizeWorkflowEdgeSourceHandle,
3939
normalizeWorkflowEdgeTargetHandle,
4040
} from '@sim/workflow-types/workflow'
41-
import { and, eq, inArray, isNull, or, sql } from 'drizzle-orm'
41+
import { and, eq, inArray, isNull, or, type SQL, sql } from 'drizzle-orm'
42+
import type { PgInsertValue } from 'drizzle-orm/pg-core'
4243
import { drizzle } from 'drizzle-orm/postgres-js'
4344
import postgres from 'postgres'
4445
import { env } from '@/env'
@@ -77,6 +78,45 @@ function canonicalizeEdgeAddCandidate(edge: EdgeAddCandidate): EdgeAddCandidate
7778
}
7879
}
7980

81+
type WorkflowScopedTable = typeof workflowBlocks | typeof workflowEdges | typeof workflowSubflows
82+
83+
/**
84+
* Inserts rows into a workflow-scoped table, updating rows that already exist
85+
* in `workflowId`. Row ids are unique across every workflow, so an upsert keyed
86+
* on `id` alone would overwrite another workflow's row: `setWhere` confines
87+
* the update to this workflow, and a conflict it skips leaves a row unreturned,
88+
* which aborts the transaction instead of dropping that row silently.
89+
*/
90+
async function upsertWorkflowRows<T extends WorkflowScopedTable>(
91+
tx: DbOrTx,
92+
table: T,
93+
workflowId: string,
94+
values: PgInsertValue<T>[],
95+
set: Partial<Record<keyof T['$inferInsert'], SQL>>
96+
) {
97+
const written = await tx
98+
.insert(table)
99+
.values(values)
100+
.onConflictDoUpdate({ target: table.id, set, setWhere: eq(table.workflowId, workflowId) })
101+
.returning({ id: table.id })
102+
103+
if (written.length !== values.length) {
104+
throw new Error('One or more ids already belong to another workflow')
105+
}
106+
}
107+
108+
const SUBFLOW_UPSERT_SET = {
109+
config: sql`excluded.config`,
110+
updatedAt: sql`now()`,
111+
} as const
112+
113+
const EDGE_UPSERT_SET = {
114+
sourceBlockId: sql`excluded.source_block_id`,
115+
targetBlockId: sql`excluded.target_block_id`,
116+
sourceHandle: sql`excluded.source_handle`,
117+
targetHandle: sql`excluded.target_handle`,
118+
} as const
119+
80120
interface FilterEdgesForPersistResult<T> {
81121
safeEdges: T[]
82122
droppedCounts: Record<string, number>
@@ -949,8 +989,8 @@ async function handleBlocksOperationTx(
949989
workflowId,
950990
type: block.type as string,
951991
name: block.name as string,
952-
positionX: (block.position as { x: number; y: number }).x,
953-
positionY: (block.position as { x: number; y: number }).y,
992+
positionX: String((block.position as { x: number; y: number }).x),
993+
positionY: String((block.position as { x: number; y: number }).y),
954994
data: (block.data as Record<string, unknown> | undefined) || {},
955995
subBlocks: mergedSubBlocks,
956996
outputs: (block.outputs as Record<string, unknown>) || {},
@@ -960,84 +1000,66 @@ async function handleBlocksOperationTx(
9601000
triggerMode: (block.triggerMode as boolean) ?? false,
9611001
errorEnabled: (block.errorEnabled as boolean) ?? false,
9621002
retry: (block.retry as Record<string, unknown> | undefined) ?? null,
963-
height: (block.height as number) || 0,
1003+
height: String((block.height as number) || 0),
9641004
locked: (block.locked as boolean) ?? false,
9651005
}
9661006
})
9671007

968-
await tx
969-
.insert(workflowBlocks)
970-
.values(blockValues)
971-
.onConflictDoUpdate({
972-
target: workflowBlocks.id,
973-
set: {
974-
type: sql`excluded.type`,
975-
name: sql`excluded.name`,
976-
positionX: sql`excluded.position_x`,
977-
positionY: sql`excluded.position_y`,
978-
enabled: sql`excluded.enabled`,
979-
horizontalHandles: sql`excluded.horizontal_handles`,
980-
advancedMode: sql`excluded.advanced_mode`,
981-
triggerMode: sql`excluded.trigger_mode`,
982-
errorEnabled: sql`excluded.error_enabled`,
983-
retry: sql`excluded.retry`,
984-
locked: sql`excluded.locked`,
985-
height: sql`excluded.height`,
986-
subBlocks: sql`excluded.sub_blocks`,
987-
outputs: sql`excluded.outputs`,
988-
data: sql`excluded.data`,
989-
updatedAt: sql`now()`,
990-
},
991-
})
1008+
await upsertWorkflowRows(tx, workflowBlocks, workflowId, blockValues, {
1009+
type: sql`excluded.type`,
1010+
name: sql`excluded.name`,
1011+
positionX: sql`excluded.position_x`,
1012+
positionY: sql`excluded.position_y`,
1013+
enabled: sql`excluded.enabled`,
1014+
horizontalHandles: sql`excluded.horizontal_handles`,
1015+
advancedMode: sql`excluded.advanced_mode`,
1016+
triggerMode: sql`excluded.trigger_mode`,
1017+
errorEnabled: sql`excluded.error_enabled`,
1018+
retry: sql`excluded.retry`,
1019+
locked: sql`excluded.locked`,
1020+
height: sql`excluded.height`,
1021+
subBlocks: sql`excluded.sub_blocks`,
1022+
outputs: sql`excluded.outputs`,
1023+
data: sql`excluded.data`,
1024+
updatedAt: sql`now()`,
1025+
})
9921026

9931027
// Create subflow entries for loop/parallel blocks (skip if already in payload)
9941028
const loopIds = new Set(loops ? Object.keys(loops) : [])
9951029
const parallelIds = new Set(parallels ? Object.keys(parallels) : [])
1030+
const defaultSubflows = []
9961031
for (const block of allowedBlocks) {
9971032
const blockId = block.id as string
9981033
if (block.type === 'loop' && !loopIds.has(blockId)) {
999-
await tx
1000-
.insert(workflowSubflows)
1001-
.values({
1002-
id: blockId,
1003-
workflowId,
1004-
type: 'loop',
1005-
config: {
1006-
loopType: 'for',
1007-
iterations: DEFAULT_LOOP_ITERATIONS,
1008-
nodes: [],
1009-
},
1010-
})
1011-
.onConflictDoUpdate({
1012-
target: workflowSubflows.id,
1013-
set: {
1014-
config: sql`excluded.config`,
1015-
updatedAt: sql`now()`,
1016-
},
1017-
})
1034+
defaultSubflows.push({
1035+
id: blockId,
1036+
workflowId,
1037+
type: 'loop',
1038+
config: { loopType: 'for', iterations: DEFAULT_LOOP_ITERATIONS, nodes: [] },
1039+
})
10181040
} else if (block.type === 'parallel' && !parallelIds.has(blockId)) {
1019-
await tx
1020-
.insert(workflowSubflows)
1021-
.values({
1022-
id: blockId,
1023-
workflowId,
1024-
type: 'parallel',
1025-
config: {
1026-
parallelType: 'count',
1027-
count: DEFAULT_PARALLEL_COUNT,
1028-
batchSize: DEFAULT_PARALLEL_BATCH_SIZE,
1029-
nodes: [],
1030-
},
1031-
})
1032-
.onConflictDoUpdate({
1033-
target: workflowSubflows.id,
1034-
set: {
1035-
config: sql`excluded.config`,
1036-
updatedAt: sql`now()`,
1037-
},
1038-
})
1041+
defaultSubflows.push({
1042+
id: blockId,
1043+
workflowId,
1044+
type: 'parallel',
1045+
config: {
1046+
parallelType: 'count',
1047+
count: DEFAULT_PARALLEL_COUNT,
1048+
batchSize: DEFAULT_PARALLEL_BATCH_SIZE,
1049+
nodes: [],
1050+
},
1051+
})
10391052
}
10401053
}
1054+
if (defaultSubflows.length > 0) {
1055+
await upsertWorkflowRows(
1056+
tx,
1057+
workflowSubflows,
1058+
workflowId,
1059+
defaultSubflows,
1060+
SUBFLOW_UPSERT_SET
1061+
)
1062+
}
10411063

10421064
const parentIds = new Set<string>()
10431065
for (const block of allowedBlocks) {
@@ -1087,18 +1109,7 @@ async function handleBlocksOperationTx(
10871109
targetHandle: normalizeWorkflowEdgeTargetHandle(edge.targetHandle),
10881110
}))
10891111

1090-
await tx
1091-
.insert(workflowEdges)
1092-
.values(edgeValues)
1093-
.onConflictDoUpdate({
1094-
target: workflowEdges.id,
1095-
set: {
1096-
sourceBlockId: sql`excluded.source_block_id`,
1097-
targetBlockId: sql`excluded.target_block_id`,
1098-
sourceHandle: sql`excluded.source_handle`,
1099-
targetHandle: sql`excluded.target_handle`,
1100-
},
1101-
})
1112+
await upsertWorkflowRows(tx, workflowEdges, workflowId, edgeValues, EDGE_UPSERT_SET)
11021113
}
11031114
}
11041115

@@ -1110,16 +1121,7 @@ async function handleBlocksOperationTx(
11101121
config: loop as Record<string, unknown>,
11111122
}))
11121123

1113-
await tx
1114-
.insert(workflowSubflows)
1115-
.values(loopValues)
1116-
.onConflictDoUpdate({
1117-
target: workflowSubflows.id,
1118-
set: {
1119-
config: sql`excluded.config`,
1120-
updatedAt: sql`now()`,
1121-
},
1122-
})
1124+
await upsertWorkflowRows(tx, workflowSubflows, workflowId, loopValues, SUBFLOW_UPSERT_SET)
11231125
}
11241126

11251127
if (parallels && Object.keys(parallels).length > 0) {
@@ -1130,16 +1132,13 @@ async function handleBlocksOperationTx(
11301132
config: parallel as Record<string, unknown>,
11311133
}))
11321134

1133-
await tx
1134-
.insert(workflowSubflows)
1135-
.values(parallelValues)
1136-
.onConflictDoUpdate({
1137-
target: workflowSubflows.id,
1138-
set: {
1139-
config: sql`excluded.config`,
1140-
updatedAt: sql`now()`,
1141-
},
1142-
})
1135+
await upsertWorkflowRows(
1136+
tx,
1137+
workflowSubflows,
1138+
workflowId,
1139+
parallelValues,
1140+
SUBFLOW_UPSERT_SET
1141+
)
11431142
}
11441143

11451144
logger.info(`Successfully batch added blocks to workflow ${workflowId}`)
@@ -1791,18 +1790,7 @@ async function handleEdgesOperationTx(
17911790
targetHandle: normalizeWorkflowEdgeTargetHandle(edge.targetHandle),
17921791
}))
17931792

1794-
await tx
1795-
.insert(workflowEdges)
1796-
.values(edgeValues)
1797-
.onConflictDoUpdate({
1798-
target: workflowEdges.id,
1799-
set: {
1800-
sourceBlockId: sql`excluded.source_block_id`,
1801-
targetBlockId: sql`excluded.target_block_id`,
1802-
sourceHandle: sql`excluded.source_handle`,
1803-
targetHandle: sql`excluded.target_handle`,
1804-
},
1805-
})
1793+
await upsertWorkflowRows(tx, workflowEdges, workflowId, edgeValues, EDGE_UPSERT_SET)
18061794

18071795
logger.debug(`Batch added ${safeEdges.length} edges to workflow ${workflowId}`)
18081796
break

‎apps/sim/app/_shell/application-loading.tsx‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { cn, SimWordmark } from '@sim/emcn'
2+
import { defaultBrandConfig } from '@/lib/branding'
23
import { DesktopTitleBarLane } from '@/app/_shell/desktop-title-bar'
34
import { type BrandConfig, getBrandConfig } from '@/ee/whitelabeling'
45

@@ -12,8 +13,6 @@ export function ApplicationLoading({
1213
brand = getBrandConfig(),
1314
fullScreen = true,
1415
}: ApplicationLoadingProps) {
15-
const wordmarkUrl = brand.wordmarkUrl || brand.logoUrl
16-
1716
return (
1817
<div
1918
role='status'
@@ -24,9 +23,9 @@ export function ApplicationLoading({
2423
)}
2524
>
2625
{fullScreen && <DesktopTitleBarLane />}
27-
{wordmarkUrl ? (
28-
<img src={wordmarkUrl} alt='' className='h-8 max-w-[240px] object-contain' />
29-
) : brand.isWhitelabeled ? (
26+
{brand.wordmarkUrl ? (
27+
<img src={brand.wordmarkUrl} alt='' className='h-8 max-w-[240px] object-contain' />
28+
) : brand.name !== defaultBrandConfig.name ? (
3029
<span className='text-[var(--text-tertiary)] text-lg'>{brand.name}</span>
3130
) : (
3231
<SimWordmark size='loading' tone='brand-muted' />

‎apps/sim/app/api/v1/auth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export async function authenticateV1Request(request: NextRequest): Promise<AuthR
7474
}
7575
}
7676

77-
await updateApiKeyLastUsed(result.keyId)
77+
updateApiKeyLastUsed(result.keyId)
7878

7979
return {
8080
authenticated: true,

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,13 +185,20 @@ export const POST = withRouteHandler(async (req: Request) => {
185185
const emailMessageId = message.message_id
186186
const inReplyTo = message.in_reply_to || null
187187

188+
// Message ids are sender-controlled and shared with every recipient of a mail,
189+
// so a task is only ever matched inside the workspace that owns this inbox.
188190
const [existingResult, isAllowed, recentCount, parentTaskResult, isEntitled] =
189191
await Promise.all([
190192
emailMessageId
191193
? db
192194
.select({ id: mothershipInboxTask.id })
193195
.from(mothershipInboxTask)
194-
.where(eq(mothershipInboxTask.emailMessageId, emailMessageId))
196+
.where(
197+
and(
198+
eq(mothershipInboxTask.workspaceId, result.id),
199+
eq(mothershipInboxTask.emailMessageId, emailMessageId)
200+
)
201+
)
195202
.limit(1)
196203
: Promise.resolve([]),
197204
isSenderAllowed(fromEmail, result.id),
@@ -200,7 +207,12 @@ export const POST = withRouteHandler(async (req: Request) => {
200207
? db
201208
.select({ chatId: mothershipInboxTask.chatId })
202209
.from(mothershipInboxTask)
203-
.where(eq(mothershipInboxTask.responseMessageId, inReplyTo))
210+
.where(
211+
and(
212+
eq(mothershipInboxTask.workspaceId, result.id),
213+
eq(mothershipInboxTask.responseMessageId, inReplyTo)
214+
)
215+
)
204216
.limit(1)
205217
: Promise.resolve([]),
206218
hasWorkspaceInboxAccess(result.id),

0 commit comments

Comments
 (0)