Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
31ed712
test(pr): hackathon (#1999)
icecrasher321 Nov 15, 2025
de91dc9
test(pr): github trigger (#2000)
icecrasher321 Nov 15, 2025
ad2a375
fix(usage-indicator): conditional rendering, upgrade, and ui/ux (#2001)
emir-karabeg Nov 15, 2025
8bd75de
fix(notes): fix notes, tighten spacing, update deprecated zustand fun…
waleedlatif1 Nov 15, 2025
d076750
fix(pdfs): use unpdf instead of pdf-parse (#2004)
waleedlatif1 Nov 15, 2025
f1111ec
fix(modals): fix z-index for various modals and output selector and v…
waleedlatif1 Nov 15, 2025
bc8947c
fix(condition): treat condition input the same as the code subblock (…
waleedlatif1 Nov 15, 2025
f8070f9
feat(models): added gpt-5.1 (#2007)
waleedlatif1 Nov 15, 2025
4bd0f31
improvement: runpath edges, blocks, active (#2008)
emir-karabeg Nov 15, 2025
b5d9964
feat(i18n): update translations (#2009)
waleedlatif1 Nov 15, 2025
dccd9e9
fix(triggers): check triggermode and consolidate block type (#2011)
Sg312 Nov 15, 2025
c25ea5c
fix(triggers): disabled trigger shouldn't be added to dag (#2012)
Sg312 Nov 15, 2025
fca92a7
fix(tags): only show start block upstream if is ancestor (#2013)
Sg312 Nov 15, 2025
949f928
fix(variables): Fix resolution on double < (#2016)
Sg312 Nov 15, 2025
d99d5fe
feat(billing): add notif for first failed payment, added upgrade emai…
waleedlatif1 Nov 16, 2025
5f446ad
feat(performance): added reactquery hooks for workflow operations, fo…
waleedlatif1 Nov 16, 2025
1c85fe9
fix(copilot): run workflow supports input format and fix run id (#2018)
Sg312 Nov 16, 2025
7e3e38a
fix(router): fix error edge in router block + fix source handle probl…
Sg312 Nov 16, 2025
e37b01b
improvement: code subblock, action bar, connections (#2024)
emir-karabeg Nov 17, 2025
bfa7c91
fix(response): fix response block http format (#2027)
Sg312 Nov 17, 2025
6f3dee8
fix(notes): fix notes block spacing, additional logs for billing tran…
waleedlatif1 Nov 17, 2025
a81f384
fix(usage-data): refetch on usage limit update in settings (#2032)
waleedlatif1 Nov 17, 2025
b5b2855
fix(overage): fix pill calculation in the usage indicator to be consi…
waleedlatif1 Nov 18, 2025
00d9b45
fix(workflows): fixed workflow loading in without start block, added …
waleedlatif1 Nov 18, 2025
98908db
fix(triggers): dedup + not surfacing deployment status log (#2033)
icecrasher321 Nov 18, 2025
3d1feab
improvement(undo-redo): expand undo-redo store to store 100 ops inste…
waleedlatif1 Nov 18, 2025
d51a756
improvement(docs): remove copy page from mobile view on docs (#2037)
waleedlatif1 Nov 18, 2025
25ac917
fix(workflow-block): clearing child workflow input format field must …
icecrasher321 Nov 18, 2025
620ce97
improvement(selectors): consolidate all integration selectors to use …
icecrasher321 Nov 18, 2025
33ca148
Merge branch 'main' into staging
icecrasher321 Nov 18, 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
10 changes: 6 additions & 4 deletions apps/docs/app/[lang]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,17 @@ export default async function Page(props: { params: Promise<{ slug?: string[]; l
component: <CustomFooter />,
}}
>
<div className='relative'>
<div className='relative mt-6 sm:mt-0'>
<div className='absolute top-1 right-0 flex items-center gap-2'>
<CopyPageButton
content={`# ${page.data.title}
<div className='hidden sm:flex'>
<CopyPageButton
content={`# ${page.data.title}

${page.data.description || ''}

${page.data.content || ''}`}
/>
/>
</div>
<PageNavigationArrows previous={neighbours?.previous} next={neighbours?.next} />
</div>
<DocsTitle>{page.data.title}</DocsTitle>
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/docs-layout/sidebar-components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function SidebarFolder({
</Link>
<button
onClick={() => setOpen(!open)}
className='rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
className='cursor-pointer rounded p-1 transition-colors hover:bg-gray-100/60 dark:hover:bg-gray-800/40'
aria-label={open ? 'Collapse' : 'Expand'}
>
<ChevronRight
Expand All @@ -84,7 +84,7 @@ export function SidebarFolder({
<button
onClick={() => setOpen(!open)}
className={cn(
'flex w-full items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
'flex w-full cursor-pointer items-center justify-between rounded-md px-2.5 py-1.5 text-left font-medium text-[13px] leading-tight transition-colors',
'hover:bg-gray-100/60 dark:hover:bg-gray-800/40',
'text-gray-800 dark:text-gray-200'
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/ui/code-block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function CodeBlock(props: React.ComponentProps<typeof FumadocsCodeBlock>)
if (pre) handleCopy(pre.textContent || '')
}}
className={cn(
'rounded-md p-2 transition-all',
'cursor-pointer rounded-md p-2 transition-all',
'border border-border bg-background/80 hover:bg-muted',
'backdrop-blur-sm'
)}
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/ui/copy-page-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function CopyPageButton({ content }: CopyPageButtonProps) {
return (
<button
onClick={handleCopy}
className='flex items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
className='flex cursor-pointer items-center gap-1.5 rounded-lg border border-border/40 bg-background px-2.5 py-2 text-muted-foreground/60 text-sm leading-none transition-all hover:border-border hover:bg-accent/50 hover:text-muted-foreground'
aria-label={copied ? 'Copied to clipboard' : 'Copy page content'}
>
{copied ? (
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/ui/language-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function LanguageDropdown() {
aria-haspopup='listbox'
aria-expanded={isOpen}
aria-controls='language-menu'
className='flex items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
className='flex cursor-pointer items-center gap-1.5 rounded-xl px-3 py-2 font-normal text-[0.9375rem] text-foreground/60 leading-[1.4] transition-colors hover:bg-foreground/8 hover:text-foreground focus:outline-none focus-visible:ring-2 focus-visible:ring-ring'
style={{
fontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
Expand Down Expand Up @@ -110,7 +110,7 @@ export function LanguageDropdown() {
}}
role='option'
aria-selected={currentLang === code}
className={`flex w-full items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
className={`flex w-full cursor-pointer items-center gap-3 px-3 py-3 text-base transition-colors first:rounded-t-xl last:rounded-b-xl hover:bg-muted/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring md:gap-2 md:px-2.5 md:py-2 md:text-sm ${
currentLang === code ? 'bg-muted/60 font-medium text-primary' : 'text-foreground'
}`}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/components/ui/search-trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function SearchTrigger() {
return (
<button
type='button'
className='flex h-10 w-[460px] items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
className='flex h-10 w-[460px] cursor-pointer items-center gap-2 rounded-xl border border-border/50 px-3 py-2 text-sm backdrop-blur-xl transition-colors hover:border-border'
style={{
backgroundColor: 'hsla(0, 0%, 5%, 0.85)',
backdropFilter: 'blur(33px) saturate(180%)',
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/components/ui/theme-toggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function ThemeToggle() {

if (!mounted) {
return (
<button className='flex items-center justify-center rounded-md p-1 text-muted-foreground'>
<button className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground'>
<Moon className='h-4 w-4' />
</button>
)
Expand All @@ -23,7 +23,7 @@ export function ThemeToggle() {
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
className='flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
className='flex cursor-pointer items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:text-foreground'
aria-label='Toggle theme'
>
{theme === 'dark' ? <Moon className='h-4 w-4' /> : <Sun className='h-4 w-4' />}
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/(landing)/components/nav/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface NavProps {
}

export default function Nav({ hideAuthButtons = false, variant = 'landing' }: NavProps = {}) {
const [githubStars, setGithubStars] = useState('18k')
const [githubStars, setGithubStars] = useState('18.5k')
const [isHovered, setIsHovered] = useState(false)
const [isLoginHovered, setIsLoginHovered] = useState(false)
const router = useRouter()
Expand Down
38 changes: 19 additions & 19 deletions apps/sim/app/api/auth/oauth/microsoft/file/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { createLogger } from '@/lib/logs/console/logger'
import { validateMicrosoftGraphId } from '@/lib/security/input-validation'
import { generateRequestId } from '@/lib/utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

Expand All @@ -15,15 +12,10 @@ const logger = createLogger('MicrosoftFileAPI')
export async function GET(request: NextRequest) {
const requestId = generateRequestId()
try {
const session = await getSession()

if (!session?.user?.id) {
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
}

const { searchParams } = new URL(request.url)
const credentialId = searchParams.get('credentialId')
const fileId = searchParams.get('fileId')
const workflowId = searchParams.get('workflowId') || undefined

if (!credentialId || !fileId) {
return NextResponse.json({ error: 'Credential ID and File ID are required' }, { status: 400 })
Expand All @@ -35,19 +27,27 @@ export async function GET(request: NextRequest) {
return NextResponse.json({ error: fileIdValidation.error }, { status: 400 })
}

const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
const authz = await authorizeCredentialUse(request, {
credentialId,
workflowId,
requireWorkflowIdForInternal: false,
})

if (!credentials.length) {
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
if (!authz.ok || !authz.credentialOwnerUserId) {
const status = authz.error === 'Credential not found' ? 404 : 403
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status })
}

const credential = credentials[0]

if (credential.userId !== session.user.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
const credential = await getCredential(requestId, credentialId, authz.credentialOwnerUserId)
if (!credential) {
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
}

const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
const accessToken = await refreshAccessTokenIfNeeded(
credentialId,
authz.credentialOwnerUserId,
requestId
)

if (!accessToken) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
Expand Down
48 changes: 19 additions & 29 deletions apps/sim/app/api/auth/oauth/microsoft/files/route.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { db } from '@sim/db'
import { account } from '@sim/db/schema'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { createLogger } from '@/lib/logs/console/logger'
import { generateRequestId } from '@/lib/utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
import { getCredential, refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

Expand All @@ -18,46 +15,39 @@ export async function GET(request: NextRequest) {
const requestId = generateRequestId()

try {
// Get the session
const session = await getSession()

// Check if the user is authenticated
if (!session?.user?.id) {
logger.warn(`[${requestId}] Unauthenticated request rejected`)
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
}

// Get the credential ID from the query params
const { searchParams } = new URL(request.url)
const credentialId = searchParams.get('credentialId')
const query = searchParams.get('query') || ''
const workflowId = searchParams.get('workflowId') || undefined

if (!credentialId) {
logger.warn(`[${requestId}] Missing credential ID`)
return NextResponse.json({ error: 'Credential ID is required' }, { status: 400 })
}

// Get the credential from the database
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
const authz = await authorizeCredentialUse(request, {
credentialId,
workflowId,
requireWorkflowIdForInternal: false,
})

if (!credentials.length) {
logger.warn(`[${requestId}] Credential not found`, { credentialId })
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
if (!authz.ok || !authz.credentialOwnerUserId) {
const status = authz.error === 'Credential not found' ? 404 : 403
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status })
}

const credential = credentials[0]

// Check if the credential belongs to the user
if (credential.userId !== session.user.id) {
logger.warn(`[${requestId}] Unauthorized credential access attempt`, {
credentialUserId: credential.userId,
requestUserId: session.user.id,
})
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
const credential = await getCredential(requestId, credentialId, authz.credentialOwnerUserId)
if (!credential) {
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
}

// Refresh access token if needed using the utility function
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
const accessToken = await refreshAccessTokenIfNeeded(
credentialId,
authz.credentialOwnerUserId,
requestId
)

if (!accessToken) {
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
Expand Down
12 changes: 10 additions & 2 deletions apps/sim/app/api/cron/renew-subscriptions/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { db } from '@sim/db'
import { webhook as webhookTable, workflow as workflowTable } 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 { verifyCronAuth } from '@/lib/auth/internal'
import { createLogger } from '@/lib/logs/console/logger'
Expand Down Expand Up @@ -35,7 +35,15 @@ export async function GET(request: NextRequest) {
})
.from(webhookTable)
.innerJoin(workflowTable, eq(webhookTable.workflowId, workflowTable.id))
.where(and(eq(webhookTable.isActive, true), eq(webhookTable.provider, 'microsoftteams')))
.where(
and(
eq(webhookTable.isActive, true),
or(
eq(webhookTable.provider, 'microsoft-teams'),
eq(webhookTable.provider, 'microsoftteams')
)
)
)

logger.info(
`Found ${webhooksWithWorkflows.length} active Teams webhooks, checking for expiring subscriptions`
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export async function POST(request: NextRequest) {
const isCredentialBased = credentialBasedProviders.includes(provider)
// Treat Microsoft Teams chat subscription as credential-based for path generation purposes
const isMicrosoftTeamsChatSubscription =
provider === 'microsoftteams' &&
provider === 'microsoft-teams' &&
typeof providerConfig === 'object' &&
providerConfig?.triggerId === 'microsoftteams_chat_subscription'

Expand Down Expand Up @@ -297,7 +297,7 @@ export async function POST(request: NextRequest) {
}
}

if (provider === 'microsoftteams') {
if (provider === 'microsoft-teams') {
const { createTeamsSubscription } = await import('@/lib/webhooks/webhook-helpers')
logger.info(`[${requestId}] Creating Teams subscription before saving to database`)
try {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/webhooks/test/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ export async function GET(request: NextRequest) {
})
}

case 'microsoftteams': {
case 'microsoft-teams': {
const hmacSecret = providerConfig.hmacSecret

if (!hmacSecret) {
Expand Down
30 changes: 2 additions & 28 deletions apps/sim/app/api/webhooks/trigger/[path]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { createLogger } from '@/lib/logs/console/logger'
import { LoggingSession } from '@/lib/logs/execution/logging-session'
import { generateRequestId } from '@/lib/utils'
import {
checkRateLimits,
Expand Down Expand Up @@ -139,34 +137,10 @@ export async function POST(
if (foundWebhook.blockId) {
const blockExists = await blockExistsInDeployment(foundWorkflow.id, foundWebhook.blockId)
if (!blockExists) {
logger.warn(
logger.info(
`[${requestId}] Trigger block ${foundWebhook.blockId} not found in deployment for workflow ${foundWorkflow.id}`
)

const executionId = uuidv4()
const loggingSession = new LoggingSession(foundWorkflow.id, executionId, 'webhook', requestId)

const actorUserId = foundWorkflow.workspaceId
? (await import('@/lib/workspaces/utils')).getWorkspaceBilledAccountUserId(
foundWorkflow.workspaceId
) || foundWorkflow.userId
: foundWorkflow.userId

await loggingSession.safeStart({
userId: actorUserId,
workspaceId: foundWorkflow.workspaceId || '',
variables: {},
})

await loggingSession.safeCompleteWithError({
error: {
message: `Trigger block not deployed. The webhook trigger (block ${foundWebhook.blockId}) is not present in the deployed workflow. Please redeploy the workflow.`,
stackTrace: undefined,
},
traceSpans: [],
})

return new NextResponse('Trigger block not deployed', { status: 404 })
return new NextResponse('Trigger block not found in deployment', { status: 404 })
}
}

Expand Down
Loading
Loading