Skip to content

Commit 8f96452

Browse files
committed
fix(workflows): resolve selection labels from saved snapshots
2 parents f6cda93 + f7b47bc commit 8f96452

513 files changed

Lines changed: 3850 additions & 18922 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/components/ui/block-info-card.tsx‎

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client'
22

33
import type * as React from 'react'
4+
import { isLightTileColor } from '@sim/workflow-renderer/tile-icon-color'
45
import { blockTypeToIconMap } from '@/components/ui/icon-mapping'
56

67
interface BlockInfoCardProps {
@@ -9,33 +10,6 @@ interface BlockInfoCardProps {
910
icon?: React.ComponentType<{ className?: string }>
1011
}
1112

12-
/**
13-
* Brightness above which a tile background is "clearly light" and a white
14-
* foreground icon would wash out. Mirrors apps/sim's LIGHT_TILE_THRESHOLD
15-
* (blocks/icon-color.ts) so monochrome `currentColor` icons (e.g. Daytona,
16-
* Notion) stay legible on white/pale tiles instead of white-on-white.
17-
*/
18-
const LIGHT_TILE_THRESHOLD = 0.75
19-
20-
function isLightTileColor(color: string): boolean {
21-
const hex = color.trim().replace('#', '').toLowerCase()
22-
let r: number
23-
let g: number
24-
let b: number
25-
if (/^[0-9a-f]{3}$/.test(hex)) {
26-
r = Number.parseInt(hex[0] + hex[0], 16)
27-
g = Number.parseInt(hex[1] + hex[1], 16)
28-
b = Number.parseInt(hex[2] + hex[2], 16)
29-
} else if (/^[0-9a-f]{6}$/.test(hex)) {
30-
r = Number.parseInt(hex.slice(0, 2), 16)
31-
g = Number.parseInt(hex.slice(2, 4), 16)
32-
b = Number.parseInt(hex.slice(4, 6), 16)
33-
} else {
34-
return false
35-
}
36-
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > LIGHT_TILE_THRESHOLD
37-
}
38-
3913
export function BlockInfoCard({
4014
type,
4115
color,

‎apps/docs/openapi-v2-workflows.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9275,14 +9275,14 @@
92759275
},
92769276
"base": {
92779277
"type": "integer",
9278-
"minimum": -9007199254740991,
9279-
"maximum": 9007199254740991,
9278+
"minimum": 1,
9279+
"maximum": 2147483647,
92809280
"description": "Base deployment version."
92819281
},
92829282
"target": {
92839283
"type": "integer",
9284-
"minimum": -9007199254740991,
9285-
"maximum": 9007199254740991,
9284+
"minimum": 1,
9285+
"maximum": 2147483647,
92869286
"description": "Target deployment version."
92879287
},
92889288
"diff": {

‎apps/realtime/src/auth.ts‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
import { createVerifyAuth } from '@sim/auth/verify'
22
import { env } from '@/env'
33

4-
export const ANONYMOUS_USER_ID = '00000000-0000-0000-0000-000000000000'
5-
6-
export const ANONYMOUS_USER = {
7-
id: ANONYMOUS_USER_ID,
8-
name: 'Anonymous',
9-
email: 'anonymous@localhost',
10-
emailVerified: true,
11-
image: null,
12-
} as const
13-
144
export const auth = createVerifyAuth({
155
secret: env.BETTER_AUTH_SECRET,
166
baseURL: env.BETTER_AUTH_URL,

‎apps/realtime/src/middleware/auth.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { ANONYMOUS_USER, ANONYMOUS_USER_ID } from '@sim/auth/principal'
12
import { createLogger } from '@sim/logger'
23
import { toError } from '@sim/utils/errors'
34
import type { Socket } from 'socket.io'
4-
import { ANONYMOUS_USER, ANONYMOUS_USER_ID, auth } from '@/auth'
5+
import { auth } from '@/auth'
56
import { isAuthDisabled } from '@/env'
67

78
const logger = createLogger('SocketAuth')

‎apps/sim/app/api/auth/oauth2/callback/instagram/route.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { db } from '@sim/db'
22
import { account } from '@sim/db/schema'
3+
import { EnvCapabilityConfigurationError } from '@sim/deployment-config/env-capabilities'
34
import { createLogger } from '@sim/logger'
45
import { generateId } from '@sim/utils/id'
56
import { and, eq } from 'drizzle-orm'
67
import { type NextRequest, NextResponse } from 'next/server'
78
import { instagramCallbackContract } from '@/lib/api/contracts/oauth-connections'
89
import { parseRequest } from '@/lib/api/server'
910
import { getSession } from '@/lib/auth'
10-
import { EnvCapabilityConfigurationError } from '@/lib/core/config/env-capabilities'
1111
import { requireConfiguredOAuthClient } from '@/lib/core/config/env-capabilities.server'
1212
import {
1313
DEFAULT_MAX_ERROR_BODY_BYTES,

‎apps/sim/app/api/auth/oauth2/callback/shopify/route.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { EnvCapabilityConfigurationError } from '@sim/deployment-config/env-capabilities'
12
import { createLogger } from '@sim/logger'
23
import { safeCompare } from '@sim/security/compare'
34
import { hmacSha256Hex } from '@sim/security/hmac'
@@ -7,7 +8,6 @@ import {
78
shopifyShopDomainSchema,
89
} from '@/lib/api/contracts/oauth-connections'
910
import { getSession } from '@/lib/auth'
10-
import { EnvCapabilityConfigurationError } from '@/lib/core/config/env-capabilities'
1111
import { requireConfiguredOAuthClient } from '@/lib/core/config/env-capabilities.server'
1212
import { getBaseUrl } from '@/lib/core/utils/urls'
1313
import { isSameOrigin } from '@/lib/core/utils/validation'

‎apps/sim/app/api/chat/utils.ts‎

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,30 +26,6 @@ export async function setChatAuthCookie(
2626
})
2727
}
2828

29-
/**
30-
* Check if user has permission to create a chat for a specific workflow
31-
*/
32-
export async function checkWorkflowAccessForChatCreation(
33-
workflowId: string,
34-
userId: string
35-
): Promise<{ hasAccess: boolean; workflow?: any }> {
36-
const authorization = await authorizeWorkflowByWorkspacePermission({
37-
workflowId,
38-
userId,
39-
action: 'admin',
40-
})
41-
42-
if (!authorization.workflow) {
43-
return { hasAccess: false }
44-
}
45-
46-
if (authorization.allowed) {
47-
return { hasAccess: true, workflow: authorization.workflow }
48-
}
49-
50-
return { hasAccess: false }
51-
}
52-
5329
/**
5430
* Check if user has access to view/edit/delete a specific chat
5531
*/
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createMockRequest } from '@sim/testing'
2+
import { asyncJobsMock, asyncJobsMockFns } from '@sim/testing/mocks/async-jobs.mock'
3+
import { authInternalMock, authInternalMockFns } from '@sim/testing/mocks/auth-internal.mock'
4+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5+
6+
vi.mock('@/lib/auth/internal', () => authInternalMock)
7+
vi.mock('@/lib/core/async-jobs', () => asyncJobsMock)
8+
9+
import { GET } from '@/app/api/cron/cleanup-file-versions/route'
10+
11+
const { mockVerifyCronAuth } = authInternalMockFns
12+
13+
const mockEnqueue = asyncJobsMockFns.mockJobQueue.enqueue
14+
15+
function request() {
16+
return createMockRequest(
17+
'GET',
18+
undefined,
19+
{},
20+
'http://localhost:3000/api/cron/cleanup-file-versions'
21+
)
22+
}
23+
24+
/** The job id a dispatch was keyed to, as the queue reports it back. */
25+
async function dispatchedJobId(): Promise<string> {
26+
const response = await GET(request())
27+
expect(response.status).toBe(200)
28+
const body = (await response.json()) as { jobId: string }
29+
return body.jobId
30+
}
31+
32+
describe('file version cleanup route', () => {
33+
beforeEach(() => {
34+
vi.useFakeTimers()
35+
vi.setSystemTime(new Date('2026-10-01T03:30:00Z'))
36+
mockVerifyCronAuth.mockReturnValue(null)
37+
mockEnqueue.mockReset()
38+
})
39+
40+
afterEach(() => {
41+
vi.useRealTimers()
42+
})
43+
44+
it('fails the cron invocation when the dispatch cannot be enqueued', async () => {
45+
mockEnqueue.mockRejectedValueOnce(new Error('queue unavailable'))
46+
47+
const response = await GET(request())
48+
49+
expect(response.status).toBe(500)
50+
})
51+
52+
describe('with a queue that keys each job by the id it is given', () => {
53+
beforeEach(() => {
54+
mockEnqueue.mockImplementation(
55+
async (_type: string, _payload: unknown, options: { jobId: string }) => options.jobId
56+
)
57+
})
58+
59+
it('dispatches a retry on the same day as the same job', async () => {
60+
const first = await dispatchedJobId()
61+
vi.setSystemTime(new Date('2026-10-01T23:59:59.999Z'))
62+
63+
expect(await dispatchedJobId()).toBe(first)
64+
})
65+
66+
it('dispatches a new job on the next day', async () => {
67+
const first = await dispatchedJobId()
68+
vi.setSystemTime(new Date('2026-10-02T00:00:00.000Z'))
69+
70+
expect(await dispatchedJobId()).not.toBe(first)
71+
})
72+
})
73+
})

‎apps/sim/app/api/cron/cleanup-file-versions/route.ts‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { verifyCronAuth } from '@/lib/auth/internal'
4-
import { dispatchCleanupJobs } from '@/lib/billing/cleanup-dispatcher'
4+
import { getJobQueue } from '@/lib/core/async-jobs'
55
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
6+
import { CLEANUP_DISPATCH_MAX_ATTEMPTS } from '@/background/cleanup-dispatch'
67

78
export const dynamic = 'force-dynamic'
89

910
const logger = createLogger('FileVersionCleanupAPI')
11+
const FILE_VERSION_CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000
1012

1113
/** GET /api/cron/cleanup-file-versions — dispatch retention for superseded workspace file versions. */
1214
export const GET = withRouteHandler(async (request: NextRequest) => {
1315
try {
1416
const authError = verifyCronAuth(request, 'file version cleanup')
1517
if (authError) return authError
1618

17-
const result = await dispatchCleanupJobs('cleanup-file-versions')
19+
const queue = await getJobQueue()
20+
const scheduleWindow = Math.floor(Date.now() / FILE_VERSION_CLEANUP_INTERVAL_MS)
21+
const jobId = await queue.enqueue(
22+
'cleanup-dispatch',
23+
{ jobType: 'cleanup-file-versions' },
24+
{
25+
maxAttempts: CLEANUP_DISPATCH_MAX_ATTEMPTS,
26+
jobId: `cleanup-dispatch:cleanup-file-versions:${scheduleWindow}`,
27+
name: 'File version cleanup dispatch',
28+
concurrencyKey: 'cleanup-dispatch:cleanup-file-versions',
29+
concurrencyLimit: 1,
30+
runner: async () => {
31+
const { dispatchCleanupJobs } = await import('@/lib/billing/cleanup-dispatcher')
32+
return dispatchCleanupJobs('cleanup-file-versions')
33+
},
34+
}
35+
)
1836

19-
logger.info('File version cleanup jobs dispatched', result)
20-
21-
return NextResponse.json({ triggered: true, ...result })
37+
logger.info('File version cleanup dispatch enqueued', { jobId })
38+
return NextResponse.json({ triggered: true, jobId })
2239
} catch (error) {
2340
logger.error('Failed to dispatch file version cleanup jobs:', { error })
2441
return NextResponse.json({ error: 'Failed to dispatch file version cleanup' }, { status: 500 })

0 commit comments

Comments
 (0)