Skip to content

Commit b126290

Browse files
committed
Merge branch 'staging' into feat/org-improv-big
2 parents 519081e + 2ae1ad2 commit b126290

126 files changed

Lines changed: 3131 additions & 1876 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.

.claude/rules/sim-testing.md

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
1313
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
1414

1515
- `@sim/db``databaseMock`
16+
- `@sim/db/schema``schemaMock`
1617
- `drizzle-orm``drizzleOrmMock`
1718
- `@sim/logger``loggerMock`
19+
- `@/lib/auth``authMock`
20+
- `@/lib/auth/hybrid``hybridAuthMock` (with default session-delegating behavior)
21+
- `@/lib/core/utils/request``requestUtilsMock`
1822
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
1923
- `@/blocks/registry`
2024
- `@trigger.dev/sdk`
@@ -192,24 +196,38 @@ hybridAuthMockFns.mockCheckSessionOrInternalAuth.mockResolvedValue({
192196

193197
### Database chain mocking
194198

199+
Use the centralized `dbChainMock` + `dbChainMockFns` helpers — no `vi.hoisted()` or chain-wiring boilerplate needed.
200+
195201
```typescript
196-
const { mockSelect, mockFrom, mockWhere } = vi.hoisted(() => ({
197-
mockSelect: vi.fn(),
198-
mockFrom: vi.fn(),
199-
mockWhere: vi.fn(),
200-
}))
202+
import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing'
201203

202-
vi.mock('@sim/db', () => ({
203-
db: { select: mockSelect },
204-
}))
204+
vi.mock('@sim/db', () => dbChainMock)
205+
// Spread for custom exports: vi.mock('@sim/db', () => ({ ...dbChainMock, myTable: {...} }))
205206

206207
beforeEach(() => {
207-
mockSelect.mockReturnValue({ from: mockFrom })
208-
mockFrom.mockReturnValue({ where: mockWhere })
209-
mockWhere.mockResolvedValue([{ id: '1', name: 'test' }])
208+
vi.clearAllMocks()
209+
resetDbChainMock() // only needed if tests use permanent (non-`Once`) overrides
210+
})
211+
212+
it('reads a row', async () => {
213+
dbChainMockFns.limit.mockResolvedValueOnce([{ id: '1', name: 'test' }])
214+
// exercise code that hits db.select().from().where().limit()
215+
expect(dbChainMockFns.where).toHaveBeenCalled()
210216
})
211217
```
212218

219+
**Default chains supported:**
220+
- `select()/selectDistinct()/selectDistinctOn() → from() → where()/innerJoin()/leftJoin() → where() → limit()/orderBy()/returning()/groupBy()`
221+
- `insert() → values() → returning()/onConflictDoUpdate()/onConflictDoNothing()`
222+
- `update() → set() → where() → limit()/orderBy()/returning()`
223+
- `delete() → where() → limit()/orderBy()/returning()`
224+
- `db.execute()` resolves `[]`
225+
- `db.transaction(cb)` calls cb with `dbChainMock.db`
226+
227+
All terminals default to `Promise.resolve([])`. Override per-test with `dbChainMockFns.<terminal>.mockResolvedValueOnce(...)`.
228+
229+
Use `resetDbChainMock()` in `beforeEach` only when tests replace wiring with `.mockReturnValue` / `.mockResolvedValue` (permanent). Tests using only `...Once` variants don't need it.
230+
213231
## @sim/testing Package
214232

215233
Always prefer over local test data.

.cursor/rules/sim-testing.mdc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ Use Vitest. Test files: `feature.ts` → `feature.test.ts`
1313
These modules are mocked globally — do NOT re-mock them in test files unless you need to override behavior:
1414

1515
- `@sim/db` → `databaseMock`
16+
- `@sim/db/schema` → `schemaMock`
1617
- `drizzle-orm` → `drizzleOrmMock`
1718
- `@sim/logger` → `loggerMock`
19+
- `@/lib/auth` → `authMock`
20+
- `@/lib/auth/hybrid` → `hybridAuthMock` (with default session-delegating behavior)
21+
- `@/lib/core/utils/request` → `requestUtilsMock`
1822
- `@/stores/console/store`, `@/stores/terminal`, `@/stores/execution/store`
1923
- `@/blocks/registry`
2024
- `@trigger.dev/sdk`

apps/sim/app/(auth)/signup/signup-form.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { usePostHog } from 'posthog-js/react'
1010
import { Input, Label } from '@/components/emcn'
1111
import { client, useSession } from '@/lib/auth/auth-client'
1212
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
13+
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
1314
import { cn } from '@/lib/core/utils/cn'
1415
import { quickValidateEmail } from '@/lib/messaging/email/validation'
1516
import { captureClientEvent, captureEvent } from '@/lib/posthog/client'
@@ -102,10 +103,14 @@ function SignupFormContent({ githubAvailable, googleAvailable, isProduction }: S
102103
useEffect(() => {
103104
setTurnstileSiteKey(getEnv('NEXT_PUBLIC_TURNSTILE_SITE_KEY'))
104105
}, [])
105-
const redirectUrl = useMemo(
106-
() => searchParams.get('redirect') || searchParams.get('callbackUrl') || '',
107-
[searchParams]
108-
)
106+
const rawRedirectUrl = searchParams.get('redirect') || searchParams.get('callbackUrl') || ''
107+
const isValidRedirectUrl = rawRedirectUrl ? validateCallbackUrl(rawRedirectUrl) : false
108+
const invalidCallbackRef = useRef(false)
109+
if (rawRedirectUrl && !isValidRedirectUrl && !invalidCallbackRef.current) {
110+
invalidCallbackRef.current = true
111+
logger.warn('Invalid callback URL detected and blocked:', { url: rawRedirectUrl })
112+
}
113+
const redirectUrl = isValidRedirectUrl ? rawRedirectUrl : ''
109114
const isInviteFlow = useMemo(
110115
() =>
111116
searchParams.get('invite_flow') === 'true' ||

apps/sim/app/(auth)/verify/use-verification.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
44
import { createLogger } from '@sim/logger'
55
import { useRouter, useSearchParams } from 'next/navigation'
66
import { client, useSession } from '@/lib/auth/auth-client'
7+
import { validateCallbackUrl } from '@/lib/core/security/input-validation'
78

89
const logger = createLogger('useVerification')
910

@@ -55,8 +56,11 @@ export function useVerification({
5556
}
5657

5758
const storedRedirectUrl = sessionStorage.getItem('inviteRedirectUrl')
58-
if (storedRedirectUrl) {
59+
if (storedRedirectUrl && validateCallbackUrl(storedRedirectUrl)) {
5960
setRedirectUrl(storedRedirectUrl)
61+
} else if (storedRedirectUrl) {
62+
logger.warn('Ignoring unsafe stored invite redirect URL', { url: storedRedirectUrl })
63+
sessionStorage.removeItem('inviteRedirectUrl')
6064
}
6165

6266
const storedIsInviteFlow = sessionStorage.getItem('isInviteFlow')
@@ -67,7 +71,11 @@ export function useVerification({
6771

6872
const redirectParam = searchParams.get('redirectAfter')
6973
if (redirectParam) {
70-
setRedirectUrl(redirectParam)
74+
if (validateCallbackUrl(redirectParam)) {
75+
setRedirectUrl(redirectParam)
76+
} else {
77+
logger.warn('Ignoring unsafe redirectAfter parameter', { url: redirectParam })
78+
}
7179
}
7280

7381
const inviteFlowParam = searchParams.get('invite_flow')

apps/sim/app/(landing)/actions/github.ts

Lines changed: 0 additions & 26 deletions
This file was deleted.

apps/sim/app/(landing)/components/navbar/components/github-stars.tsx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
'use client'
22

3-
import { useEffect, useState } from 'react'
4-
import { createLogger } from '@sim/logger'
53
import { GithubOutlineIcon } from '@/components/icons'
6-
import { getFormattedGitHubStars } from '@/app/(landing)/actions/github'
7-
8-
const logger = createLogger('github-stars')
9-
10-
const INITIAL_STARS = '27.7k'
4+
import { useGitHubStars } from '@/hooks/queries/github-stars'
115

126
/**
137
* Client component that displays GitHub stars count.
@@ -16,15 +10,7 @@ const INITIAL_STARS = '27.7k'
1610
* a Server Component for optimal SEO/GEO crawlability.
1711
*/
1812
export function GitHubStars() {
19-
const [stars, setStars] = useState(INITIAL_STARS)
20-
21-
useEffect(() => {
22-
getFormattedGitHubStars()
23-
.then(setStars)
24-
.catch((error) => {
25-
logger.warn('Failed to fetch GitHub stars', error)
26-
})
27-
}, [])
13+
const { data: stars } = useGitHubStars()
2814

2915
return (
3016
<a
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from 'next'
2+
import Link from 'next/link'
3+
import { AUTH_PRIMARY_CTA_BASE } from '@/app/(auth)/components/auth-button-classes'
4+
5+
export const metadata: Metadata = {
6+
title: 'Page Not Found',
7+
robots: { index: false, follow: true },
8+
}
9+
10+
export default function IntegrationsNotFound() {
11+
return (
12+
<div className='flex min-h-[60vh] items-center justify-center px-4 py-24'>
13+
<div className='flex flex-col items-center gap-3'>
14+
<h1 className='text-balance font-[430] font-season text-[40px] text-white leading-[110%] tracking-[-0.02em]'>
15+
Page not found
16+
</h1>
17+
<p className='font-[430] font-season text-[color-mix(in_srgb,var(--landing-text-subtle)_60%,transparent)] text-lg leading-[125%] tracking-[0.02em]'>
18+
The page you&apos;re looking for doesn&apos;t exist or has been moved.
19+
</p>
20+
<div className='mt-3 flex items-center gap-2'>
21+
<Link href='/' className={AUTH_PRIMARY_CTA_BASE}>
22+
Return to Home
23+
</Link>
24+
</div>
25+
</div>
26+
</div>
27+
)
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Metadata } from 'next'
2+
import Link from 'next/link'
3+
import { AUTH_PRIMARY_CTA_BASE } from '@/app/(auth)/components/auth-button-classes'
4+
5+
export const metadata: Metadata = {
6+
title: 'Page Not Found',
7+
robots: { index: false, follow: true },
8+
}
9+
10+
export default function ModelsNotFound() {
11+
return (
12+
<div className='flex min-h-[60vh] items-center justify-center px-4 py-24'>
13+
<div className='flex flex-col items-center gap-3'>
14+
<h1 className='text-balance font-[430] font-season text-[40px] text-white leading-[110%] tracking-[-0.02em]'>
15+
Page not found
16+
</h1>
17+
<p className='font-[430] font-season text-[color-mix(in_srgb,var(--landing-text-subtle)_60%,transparent)] text-lg leading-[125%] tracking-[0.02em]'>
18+
The page you&apos;re looking for doesn&apos;t exist or has been moved.
19+
</p>
20+
<div className='mt-3 flex items-center gap-2'>
21+
<Link href='/' className={AUTH_PRIMARY_CTA_BASE}>
22+
Return to Home
23+
</Link>
24+
</div>
25+
</div>
26+
</div>
27+
)
28+
}

0 commit comments

Comments
 (0)