diff --git a/apps/sim/app/api/billing/route.ts b/apps/sim/app/api/billing/route.ts index 4d134bc87a..0c7a48dfca 100644 --- a/apps/sim/app/api/billing/route.ts +++ b/apps/sim/app/api/billing/route.ts @@ -9,6 +9,8 @@ import { member } from '@/db/schema' const logger = createLogger('UnifiedBillingAPI') +export const dynamic = 'force-dynamic' + /** * Unified Billing Endpoint */ diff --git a/apps/sim/app/api/chat/route.ts b/apps/sim/app/api/chat/route.ts index d144e19a0e..e802663021 100644 --- a/apps/sim/app/api/chat/route.ts +++ b/apps/sim/app/api/chat/route.ts @@ -14,6 +14,8 @@ import { chat } from '@/db/schema' const logger = createLogger('ChatAPI') +export const dynamic = 'force-dynamic' + const chatSchema = z.object({ workflowId: z.string().min(1, 'Workflow ID is required'), subdomain: z diff --git a/apps/sim/app/api/chat/subdomains/validate/route.ts b/apps/sim/app/api/chat/subdomains/validate/route.ts index 1b01b44024..7c0c4ba689 100644 --- a/apps/sim/app/api/chat/subdomains/validate/route.ts +++ b/apps/sim/app/api/chat/subdomains/validate/route.ts @@ -2,6 +2,9 @@ import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { db } from '@/db' import { chat } from '@/db/schema' diff --git a/apps/sim/app/api/environment/route.ts b/apps/sim/app/api/environment/route.ts index d76df80e4f..1233ccc411 100644 --- a/apps/sim/app/api/environment/route.ts +++ b/apps/sim/app/api/environment/route.ts @@ -3,6 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { decryptSecret, encryptSecret } from '@/lib/utils' import { db } from '@/db' import { environment } from '@/db/schema' diff --git a/apps/sim/app/api/folders/[id]/route.ts b/apps/sim/app/api/folders/[id]/route.ts index 9bb4d32875..6beb1c383a 100644 --- a/apps/sim/app/api/folders/[id]/route.ts +++ b/apps/sim/app/api/folders/[id]/route.ts @@ -2,6 +2,9 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { getUserEntityPermissions } from '@/lib/permissions/utils' import { db } from '@/db' import { workflow, workflowFolder } from '@/db/schema' diff --git a/apps/sim/app/api/folders/route.ts b/apps/sim/app/api/folders/route.ts index e207dda567..0465cc0a6f 100644 --- a/apps/sim/app/api/folders/route.ts +++ b/apps/sim/app/api/folders/route.ts @@ -8,6 +8,8 @@ import { workflowFolder } from '@/db/schema' const logger = createLogger('FoldersAPI') +export const dynamic = 'force-dynamic' + // GET - Fetch folders for a workspace export async function GET(request: NextRequest) { try { diff --git a/apps/sim/app/api/jobs/[jobId]/route.ts b/apps/sim/app/api/jobs/[jobId]/route.ts index 4a26f15b25..3738b4a399 100644 --- a/apps/sim/app/api/jobs/[jobId]/route.ts +++ b/apps/sim/app/api/jobs/[jobId]/route.ts @@ -4,6 +4,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' import { db } from '@/db' + +export const dynamic = 'force-dynamic' + import { apiKey as apiKeyTable } from '@/db/schema' import { createErrorResponse } from '../../workflows/utils' diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts index ba011e5173..2bf08b20a7 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/[chunkId]/route.ts @@ -4,6 +4,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { document, embedding } from '@/db/schema' import { checkChunkAccess } from '../../../../../utils' diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts index 776e6cd1c2..17632f25f2 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/chunks/route.ts @@ -4,6 +4,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { estimateTokenCount } from '@/lib/tokenization/estimators' import { getUserId } from '@/app/api/auth/oauth/utils' import { diff --git a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts index 6fc111e732..8a92badf44 100644 --- a/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts +++ b/apps/sim/app/api/knowledge/[id]/documents/[documentId]/route.ts @@ -3,6 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { document, embedding } from '@/db/schema' import { checkDocumentAccess, checkDocumentWriteAccess, processDocumentAsync } from '../../../utils' diff --git a/apps/sim/app/api/knowledge/search/route.test.ts b/apps/sim/app/api/knowledge/search/route.test.ts index 1824d9b708..a1405134d7 100644 --- a/apps/sim/app/api/knowledge/search/route.test.ts +++ b/apps/sim/app/api/knowledge/search/route.test.ts @@ -51,6 +51,11 @@ vi.mock('@/providers/utils', () => ({ }), })) +const mockCheckKnowledgeBaseAccess = vi.fn() +vi.mock('@/app/api/knowledge/utils', () => ({ + checkKnowledgeBaseAccess: mockCheckKnowledgeBaseAccess, +})) + mockConsoleLogger() describe('Knowledge Search API Route', () => { @@ -132,7 +137,11 @@ describe('Knowledge Search API Route', () => { it('should perform search successfully with single knowledge base', async () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) @@ -149,6 +158,10 @@ describe('Knowledge Search API Route', () => { const response = await POST(req) const data = await response.json() + if (response.status !== 200) { + console.log('Test failed with response:', data) + } + expect(response.status).toBe(200) expect(data.success).toBe(true) expect(data.data.results).toHaveLength(2) @@ -171,7 +184,10 @@ describe('Knowledge Search API Route', () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(multiKbs) + // Mock knowledge base access check to return success for both KBs + mockCheckKnowledgeBaseAccess + .mockResolvedValueOnce({ hasAccess: true, knowledgeBase: multiKbs[0] }) + .mockResolvedValueOnce({ hasAccess: true, knowledgeBase: multiKbs[1] }) mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) @@ -201,9 +217,13 @@ describe('Knowledge Search API Route', () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) // First call: get knowledge bases + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) - mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) // Second call: search results + mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) // Search results mockFetch.mockResolvedValue({ ok: true, @@ -255,7 +275,11 @@ describe('Knowledge Search API Route', () => { it('should return not found for non-existent knowledge base', async () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce([]) // No knowledge bases found + // Mock knowledge base access check to return no access + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: false, + notFound: true, + }) const req = createMockRequest('POST', validSearchData) const { POST } = await import('./route') @@ -274,7 +298,10 @@ describe('Knowledge Search API Route', () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) // Only kb-123 found + // Mock access check: first KB has access, second doesn't + mockCheckKnowledgeBaseAccess + .mockResolvedValueOnce({ hasAccess: true, knowledgeBase: mockKnowledgeBases[0] }) + .mockResolvedValueOnce({ hasAccess: false, notFound: true }) const req = createMockRequest('POST', multiKbData) const { POST } = await import('./route') @@ -282,7 +309,7 @@ describe('Knowledge Search API Route', () => { const data = await response.json() expect(response.status).toBe(404) - expect(data.error).toBe('Knowledge bases not found: kb-missing') + expect(data.error).toBe('Knowledge bases not found or access denied: kb-missing') }) it.concurrent('should validate search parameters', async () => { @@ -310,9 +337,13 @@ describe('Knowledge Search API Route', () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) // First call: get knowledge bases + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) - mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) // Second call: search results + mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) // Search results mockFetch.mockResolvedValue({ ok: true, @@ -416,7 +447,13 @@ describe('Knowledge Search API Route', () => { describe('Cost tracking', () => { it.concurrent('should include cost information in successful search response', async () => { mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) + + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) + mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) mockFetch.mockResolvedValue({ @@ -458,7 +495,13 @@ describe('Knowledge Search API Route', () => { const { calculateCost } = await import('@/providers/utils') mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) + + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) + mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) mockFetch.mockResolvedValue({ @@ -509,7 +552,13 @@ describe('Knowledge Search API Route', () => { } mockGetUserId.mockResolvedValue('user-123') - mockDbChain.where.mockResolvedValueOnce(mockKnowledgeBases) + + // Mock knowledge base access check to return success + mockCheckKnowledgeBaseAccess.mockResolvedValue({ + hasAccess: true, + knowledgeBase: mockKnowledgeBases[0], + }) + mockDbChain.limit.mockResolvedValueOnce(mockSearchResults) mockFetch.mockResolvedValue({ diff --git a/apps/sim/app/api/knowledge/search/route.ts b/apps/sim/app/api/knowledge/search/route.ts index 6ae89e8449..38be635b62 100644 --- a/apps/sim/app/api/knowledge/search/route.ts +++ b/apps/sim/app/api/knowledge/search/route.ts @@ -1,4 +1,4 @@ -import { and, eq, inArray, isNull, sql } from 'drizzle-orm' +import { and, eq, inArray, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { retryWithExponentialBackoff } from '@/lib/documents/utils' @@ -6,8 +6,9 @@ import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console-logger' import { estimateTokenCount } from '@/lib/tokenization/estimators' import { getUserId } from '@/app/api/auth/oauth/utils' +import { checkKnowledgeBaseAccess } from '@/app/api/knowledge/utils' import { db } from '@/db' -import { embedding, knowledgeBase } from '@/db/schema' +import { embedding } from '@/db/schema' import { calculateCost } from '@/providers/utils' const logger = createLogger('VectorSearchAPI') @@ -261,39 +262,37 @@ export async function POST(request: NextRequest) { ? validatedData.knowledgeBaseIds : [validatedData.knowledgeBaseIds] - const [kb, queryEmbedding] = await Promise.all([ - db - .select() - .from(knowledgeBase) - .where( - and( - inArray(knowledgeBase.id, knowledgeBaseIds), - eq(knowledgeBase.userId, userId), - isNull(knowledgeBase.deletedAt) - ) - ), - generateSearchEmbedding(validatedData.query), - ]) - - if (kb.length === 0) { + // Check access permissions for each knowledge base using proper workspace-based permissions + const accessibleKbIds: string[] = [] + for (const kbId of knowledgeBaseIds) { + const accessCheck = await checkKnowledgeBaseAccess(kbId, userId) + if (accessCheck.hasAccess) { + accessibleKbIds.push(kbId) + } + } + + if (accessibleKbIds.length === 0) { return NextResponse.json( { error: 'Knowledge base not found or access denied' }, { status: 404 } ) } - const foundKbIds = kb.map((k) => k.id) - const missingKbIds = knowledgeBaseIds.filter((id) => !foundKbIds.includes(id)) + // Generate query embedding in parallel with access checks + const queryEmbedding = await generateSearchEmbedding(validatedData.query) + + // Check if any requested knowledge bases were not accessible + const inaccessibleKbIds = knowledgeBaseIds.filter((id) => !accessibleKbIds.includes(id)) - if (missingKbIds.length > 0) { + if (inaccessibleKbIds.length > 0) { return NextResponse.json( - { error: `Knowledge bases not found: ${missingKbIds.join(', ')}` }, + { error: `Knowledge bases not found or access denied: ${inaccessibleKbIds.join(', ')}` }, { status: 404 } ) } - // Adaptive query strategy based on KB count and parameters - const strategy = getQueryStrategy(foundKbIds.length, validatedData.topK) + // Adaptive query strategy based on accessible KB count and parameters + const strategy = getQueryStrategy(accessibleKbIds.length, validatedData.topK) const queryVector = JSON.stringify(queryEmbedding) let results: any[] @@ -301,7 +300,7 @@ export async function POST(request: NextRequest) { if (strategy.useParallel) { // Execute parallel queries for better performance with many KBs const parallelResults = await executeParallelQueries( - foundKbIds, + accessibleKbIds, queryVector, validatedData.topK, strategy.distanceThreshold, @@ -311,7 +310,7 @@ export async function POST(request: NextRequest) { } else { // Execute single optimized query for fewer KBs results = await executeSingleQuery( - foundKbIds, + accessibleKbIds, queryVector, validatedData.topK, strategy.distanceThreshold, @@ -350,8 +349,8 @@ export async function POST(request: NextRequest) { similarity: 1 - result.distance, })), query: validatedData.query, - knowledgeBaseIds: foundKbIds, - knowledgeBaseId: foundKbIds[0], + knowledgeBaseIds: accessibleKbIds, + knowledgeBaseId: accessibleKbIds[0], topK: validatedData.topK, totalResults: results.length, ...(cost && tokenCount diff --git a/apps/sim/app/api/organizations/[id]/invitations/route.ts b/apps/sim/app/api/organizations/[id]/invitations/route.ts index bf14b571a8..5eda9e9868 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/route.ts @@ -21,6 +21,8 @@ import { invitation, member, organization, user, workspace, workspaceInvitation const logger = createLogger('OrganizationInvitationsAPI') +export const dynamic = 'force-dynamic' + interface WorkspaceInvitation { workspaceId: string permission: 'admin' | 'write' | 'read' diff --git a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts index d9b7d0c2d7..51754ae04d 100644 --- a/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/[memberId]/route.ts @@ -7,6 +7,8 @@ import { member, user, userStats } from '@/db/schema' const logger = createLogger('OrganizationMemberAPI') +export const dynamic = 'force-dynamic' + /** * GET /api/organizations/[id]/members/[memberId] * Get individual organization member details diff --git a/apps/sim/app/api/organizations/[id]/members/route.ts b/apps/sim/app/api/organizations/[id]/members/route.ts index 30220c83ff..a0a341d104 100644 --- a/apps/sim/app/api/organizations/[id]/members/route.ts +++ b/apps/sim/app/api/organizations/[id]/members/route.ts @@ -13,6 +13,8 @@ import { invitation, member, organization, user, userStats } from '@/db/schema' const logger = createLogger('OrganizationMembersAPI') +export const dynamic = 'force-dynamic' + /** * GET /api/organizations/[id]/members * Get organization members with optional usage data diff --git a/apps/sim/app/api/organizations/[id]/route.ts b/apps/sim/app/api/organizations/[id]/route.ts index f2ef30750d..2d406c61e3 100644 --- a/apps/sim/app/api/organizations/[id]/route.ts +++ b/apps/sim/app/api/organizations/[id]/route.ts @@ -7,6 +7,9 @@ import { updateOrganizationSeats, } from '@/lib/billing/validation/seat-management' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { member, organization } from '@/db/schema' diff --git a/apps/sim/app/api/organizations/[id]/workspaces/route.ts b/apps/sim/app/api/organizations/[id]/workspaces/route.ts index 99079431cf..8e8eba18c4 100644 --- a/apps/sim/app/api/organizations/[id]/workspaces/route.ts +++ b/apps/sim/app/api/organizations/[id]/workspaces/route.ts @@ -7,6 +7,8 @@ import { member, permissions, user, workspace } from '@/db/schema' const logger = createLogger('OrganizationWorkspacesAPI') +export const dynamic = 'force-dynamic' + /** * GET /api/organizations/[id]/workspaces * Get workspaces related to the organization with optional filtering diff --git a/apps/sim/app/api/organizations/invitations/accept/route.ts b/apps/sim/app/api/organizations/invitations/accept/route.ts index e9d616065f..6f8c52f087 100644 --- a/apps/sim/app/api/organizations/invitations/accept/route.ts +++ b/apps/sim/app/api/organizations/invitations/accept/route.ts @@ -9,6 +9,8 @@ import { invitation, member, permissions, workspaceInvitation } from '@/db/schem const logger = createLogger('OrganizationInvitationAcceptance') +export const dynamic = 'force-dynamic' + // Accept an organization invitation and any associated workspace invitations export async function GET(req: NextRequest) { const invitationId = req.nextUrl.searchParams.get('id') diff --git a/apps/sim/app/api/schedules/[id]/status/route.ts b/apps/sim/app/api/schedules/[id]/status/route.ts index 84fae524f6..d111b72d1c 100644 --- a/apps/sim/app/api/schedules/[id]/status/route.ts +++ b/apps/sim/app/api/schedules/[id]/status/route.ts @@ -2,6 +2,9 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { workflow, workflowSchedule } from '@/db/schema' diff --git a/apps/sim/app/api/schedules/route.ts b/apps/sim/app/api/schedules/route.ts index c6746a0ff0..829b666a24 100644 --- a/apps/sim/app/api/schedules/route.ts +++ b/apps/sim/app/api/schedules/route.ts @@ -17,6 +17,8 @@ import { workflowSchedule } from '@/db/schema' const logger = createLogger('ScheduledAPI') +export const dynamic = 'force-dynamic' + const ScheduleRequestSchema = z.object({ workflowId: z.string(), blockId: z.string().optional(), diff --git a/apps/sim/app/api/tools/custom/route.ts b/apps/sim/app/api/tools/custom/route.ts index 744d74cc27..fb2e9749af 100644 --- a/apps/sim/app/api/tools/custom/route.ts +++ b/apps/sim/app/api/tools/custom/route.ts @@ -9,6 +9,8 @@ import { customTools } from '@/db/schema' const logger = createLogger('CustomToolsAPI') +export const dynamic = 'force-dynamic' + // Define validation schema for custom tools const CustomToolSchema = z.object({ tools: z.array( diff --git a/apps/sim/app/api/usage-limits/route.ts b/apps/sim/app/api/usage-limits/route.ts index 4726b0fdef..7509dbd880 100644 --- a/apps/sim/app/api/usage-limits/route.ts +++ b/apps/sim/app/api/usage-limits/route.ts @@ -7,6 +7,8 @@ import { isOrganizationOwnerOrAdmin } from '@/lib/permissions/utils' const logger = createLogger('UnifiedUsageLimitsAPI') +export const dynamic = 'force-dynamic' + /** * Unified Usage Limits Endpoint * GET/PUT /api/usage-limits?context=user|member&userId=&organizationId= diff --git a/apps/sim/app/api/users/me/api-keys/[id]/route.ts b/apps/sim/app/api/users/me/api-keys/[id]/route.ts index 811a1d794a..a4cb4e7e95 100644 --- a/apps/sim/app/api/users/me/api-keys/[id]/route.ts +++ b/apps/sim/app/api/users/me/api-keys/[id]/route.ts @@ -2,6 +2,9 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { apiKey } from '@/db/schema' diff --git a/apps/sim/app/api/users/me/api-keys/route.ts b/apps/sim/app/api/users/me/api-keys/route.ts index 70b3f05d25..d4076591be 100644 --- a/apps/sim/app/api/users/me/api-keys/route.ts +++ b/apps/sim/app/api/users/me/api-keys/route.ts @@ -9,6 +9,8 @@ import { apiKey } from '@/db/schema' const logger = createLogger('ApiKeysAPI') +export const dynamic = 'force-dynamic' + // GET /api/users/me/api-keys - Get all API keys for the current user export async function GET(request: NextRequest) { try { diff --git a/apps/sim/app/api/users/me/settings/route.ts b/apps/sim/app/api/users/me/settings/route.ts index 3944b21d78..7210fea518 100644 --- a/apps/sim/app/api/users/me/settings/route.ts +++ b/apps/sim/app/api/users/me/settings/route.ts @@ -4,6 +4,9 @@ import { NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { settings } from '@/db/schema' diff --git a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts index a3ca4146c0..87b3238358 100644 --- a/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts +++ b/apps/sim/app/api/users/me/subscription/[id]/transfer/route.ts @@ -3,6 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { member, organization, subscription } from '@/db/schema' diff --git a/apps/sim/app/api/users/rate-limit/route.ts b/apps/sim/app/api/users/rate-limit/route.ts index 8be75b6a33..de5cf656e2 100644 --- a/apps/sim/app/api/users/rate-limit/route.ts +++ b/apps/sim/app/api/users/rate-limit/route.ts @@ -2,6 +2,9 @@ import { eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { apiKey as apiKeyTable, subscription } from '@/db/schema' import { RateLimiter } from '@/services/queue' diff --git a/apps/sim/app/api/workflows/[id]/duplicate/route.ts b/apps/sim/app/api/workflows/[id]/duplicate/route.ts index 890c5eb520..5e2edece58 100644 --- a/apps/sim/app/api/workflows/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workflows/[id]/duplicate/route.ts @@ -4,6 +4,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { getUserEntityPermissions } from '@/lib/permissions/utils' import { db } from '@/db' import { workflow, workflowBlocks, workflowEdges, workflowSubflows } from '@/db/schema' diff --git a/apps/sim/app/api/workflows/[id]/route.ts b/apps/sim/app/api/workflows/[id]/route.ts index d7534f41f3..8f36987e60 100644 --- a/apps/sim/app/api/workflows/[id]/route.ts +++ b/apps/sim/app/api/workflows/[id]/route.ts @@ -12,6 +12,8 @@ import { workflow } from '@/db/schema' const logger = createLogger('WorkflowByIdAPI') +export const dynamic = 'force-dynamic' + const UpdateWorkflowSchema = z.object({ name: z.string().min(1, 'Name is required').optional(), description: z.string().optional(), diff --git a/apps/sim/app/api/workflows/[id]/state/route.ts b/apps/sim/app/api/workflows/[id]/state/route.ts index db440fb816..dbc02929ee 100644 --- a/apps/sim/app/api/workflows/[id]/state/route.ts +++ b/apps/sim/app/api/workflows/[id]/state/route.ts @@ -3,6 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { getUserEntityPermissions } from '@/lib/permissions/utils' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/db-helpers' import { db } from '@/db' diff --git a/apps/sim/app/api/workflows/[id]/variables/route.ts b/apps/sim/app/api/workflows/[id]/variables/route.ts index 668fb9453c..7252588d48 100644 --- a/apps/sim/app/api/workflows/[id]/variables/route.ts +++ b/apps/sim/app/api/workflows/[id]/variables/route.ts @@ -3,6 +3,9 @@ import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { getUserEntityPermissions } from '@/lib/permissions/utils' import { db } from '@/db' import { workflow } from '@/db/schema' diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index 3e698468c1..d1b89c9bcc 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -8,6 +8,8 @@ import { workflow, workflowBlocks } from '@/db/schema' const logger = createLogger('WorkflowAPI') +export const dynamic = 'force-dynamic' + // Schema for workflow creation const CreateWorkflowSchema = z.object({ name: z.string().min(1, 'Name is required'), diff --git a/apps/sim/app/api/workflows/sync/route.ts b/apps/sim/app/api/workflows/sync/route.ts index 286af3accb..1480d0301c 100644 --- a/apps/sim/app/api/workflows/sync/route.ts +++ b/apps/sim/app/api/workflows/sync/route.ts @@ -3,6 +3,9 @@ import { and, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { getUserEntityPermissions } from '@/lib/permissions/utils' import { db } from '@/db' import { workflow, workspace } from '@/db/schema' diff --git a/apps/sim/app/api/workspaces/[id]/permissions/route.ts b/apps/sim/app/api/workspaces/[id]/permissions/route.ts index 0c8fc2877d..a60dba7d38 100644 --- a/apps/sim/app/api/workspaces/[id]/permissions/route.ts +++ b/apps/sim/app/api/workspaces/[id]/permissions/route.ts @@ -3,6 +3,9 @@ import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { getUsersWithPermissions, hasWorkspaceAdminAccess } from '@/lib/permissions/utils' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { permissions, type permissionTypeEnum } from '@/db/schema' diff --git a/apps/sim/app/api/workspaces/invitations/[id]/route.ts b/apps/sim/app/api/workspaces/invitations/[id]/route.ts index 27d0dae84b..b7ff488327 100644 --- a/apps/sim/app/api/workspaces/invitations/[id]/route.ts +++ b/apps/sim/app/api/workspaces/invitations/[id]/route.ts @@ -5,6 +5,8 @@ import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils' import { db } from '@/db' import { workspaceInvitation } from '@/db/schema' +export const dynamic = 'force-dynamic' + // DELETE /api/workspaces/invitations/[id] - Delete a workspace invitation export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id } = await params diff --git a/apps/sim/app/api/workspaces/invitations/accept/route.ts b/apps/sim/app/api/workspaces/invitations/accept/route.ts index 10d001b34c..9e6f1cec68 100644 --- a/apps/sim/app/api/workspaces/invitations/accept/route.ts +++ b/apps/sim/app/api/workspaces/invitations/accept/route.ts @@ -6,6 +6,8 @@ import { env } from '@/lib/env' import { db } from '@/db' import { permissions, user, workspace, workspaceInvitation } from '@/db/schema' +export const dynamic = 'force-dynamic' + // Accept an invitation via token export async function GET(req: NextRequest) { const token = req.nextUrl.searchParams.get('token') diff --git a/apps/sim/app/api/workspaces/invitations/details/route.ts b/apps/sim/app/api/workspaces/invitations/details/route.ts index 971c732c4a..2e5a156718 100644 --- a/apps/sim/app/api/workspaces/invitations/details/route.ts +++ b/apps/sim/app/api/workspaces/invitations/details/route.ts @@ -4,6 +4,8 @@ import { getSession } from '@/lib/auth' import { db } from '@/db' import { workspace, workspaceInvitation } from '@/db/schema' +export const dynamic = 'force-dynamic' + // Get invitation details by token export async function GET(req: NextRequest) { const token = req.nextUrl.searchParams.get('token') diff --git a/apps/sim/app/api/workspaces/members/[id]/route.ts b/apps/sim/app/api/workspaces/members/[id]/route.ts index 6d0c536e3c..e373b8eae0 100644 --- a/apps/sim/app/api/workspaces/members/[id]/route.ts +++ b/apps/sim/app/api/workspaces/members/[id]/route.ts @@ -5,6 +5,8 @@ import { hasWorkspaceAdminAccess } from '@/lib/permissions/utils' import { db } from '@/db' import { permissions } from '@/db/schema' +export const dynamic = 'force-dynamic' + // DELETE /api/workspaces/members/[id] - Remove a member from a workspace export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { const { id: userId } = await params diff --git a/apps/sim/app/api/workspaces/members/route.ts b/apps/sim/app/api/workspaces/members/route.ts index 3bbf485890..3f275e00c1 100644 --- a/apps/sim/app/api/workspaces/members/route.ts +++ b/apps/sim/app/api/workspaces/members/route.ts @@ -7,6 +7,8 @@ import { permissions, type permissionTypeEnum, user } from '@/db/schema' type PermissionType = (typeof permissionTypeEnum.enumValues)[number] +export const dynamic = 'force-dynamic' + // Add a member to a workspace export async function POST(req: Request) { const session = await getSession() diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index c9cb958c4a..dd6c59458f 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -3,6 +3,9 @@ import { and, desc, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { createLogger } from '@/lib/logs/console-logger' + +export const dynamic = 'force-dynamic' + import { db } from '@/db' import { permissions, workflow, workflowBlocks, workspace } from '@/db/schema' diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index a5b0ba61dd..8231bc5b98 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -93,7 +93,9 @@ export class WorkflowBlockHandler implements BlockHandler { }) const startTime = performance.now() - const result = await subExecutor.execute(executionId) + // Use the actual child workflow ID for authentication, not the execution ID + // This ensures knowledge base and other API calls can properly authenticate + const result = await subExecutor.execute(workflowId) const duration = performance.now() - startTime // Remove current execution from stack after completion diff --git a/apps/sim/lib/webhooks/utils.ts b/apps/sim/lib/webhooks/utils.ts index 9ea7daba43..363ae75057 100644 --- a/apps/sim/lib/webhooks/utils.ts +++ b/apps/sim/lib/webhooks/utils.ts @@ -1437,6 +1437,104 @@ export async function fetchAndProcessAirtablePayloads( /** * Process webhook verification and authorization */ +/** + * Handle Microsoft Teams webhooks with immediate acknowledgment + */ +async function processMicrosoftTeamsWebhook( + foundWebhook: any, + foundWorkflow: any, + input: any, + executionId: string, + requestId: string +): Promise { + logger.info( + `[${requestId}] Acknowledging Microsoft Teams webhook ${foundWebhook.id} and executing workflow ${foundWorkflow.id} asynchronously (Execution: ${executionId})` + ) + + // Execute workflow asynchronously without waiting for completion + executeWorkflowFromPayload( + foundWorkflow, + input, + executionId, + requestId, + foundWebhook.blockId + ).catch((error) => { + // Log any errors that occur during async execution + logger.error( + `[${requestId}] Error during async workflow execution for webhook ${foundWebhook.id} (Execution: ${executionId})`, + error + ) + }) + + // Return immediate acknowledgment for Microsoft Teams + return NextResponse.json( + { + type: 'message', + text: 'Sim Studio', + }, + { status: 200 } + ) +} + +/** + * Handle standard webhooks with synchronous execution + */ +async function processStandardWebhook( + foundWebhook: any, + foundWorkflow: any, + input: any, + executionId: string, + requestId: string +): Promise { + logger.info( + `[${requestId}] Executing workflow ${foundWorkflow.id} for webhook ${foundWebhook.id} (Execution: ${executionId})` + ) + + await executeWorkflowFromPayload( + foundWorkflow, + input, + executionId, + requestId, + foundWebhook.blockId + ) + + // Since executeWorkflowFromPayload handles logging and errors internally, + // we just need to return a standard success response for synchronous webhooks. + // Note: The actual result isn't typically returned in the webhook response itself. + + return NextResponse.json({ message: 'Webhook processed' }, { status: 200 }) +} + +/** + * Handle webhook processing errors with provider-specific responses + */ +function handleWebhookError( + error: any, + foundWebhook: any, + executionId: string, + requestId: string +): NextResponse { + logger.error( + `[${requestId}] Error in processWebhook for ${foundWebhook.id} (Execution: ${executionId})`, + error + ) + + // For Microsoft Teams outgoing webhooks, return the expected error format + if (foundWebhook.provider === 'microsoftteams') { + return NextResponse.json( + { + type: 'message', + text: 'Webhook processing failed', + }, + { status: 200 } + ) // Still return 200 to prevent Teams from showing additional error messages + } + + return new NextResponse(`Internal Server Error: ${error.message}`, { + status: 500, + }) +} + export async function processWebhook( foundWebhook: any, foundWorkflow: any, @@ -1449,11 +1547,7 @@ export async function processWebhook( // --- Handle Airtable differently - it should always use fetchAndProcessAirtablePayloads --- if (foundWebhook.provider === 'airtable') { logger.info(`[${requestId}] Routing Airtable webhook through dedicated processor`) - - // Use the dedicated Airtable payload fetcher and processor await fetchAndProcessAirtablePayloads(foundWebhook, foundWorkflow, requestId) - - // Return standard success response return NextResponse.json({ message: 'Airtable webhook processed' }, { status: 200 }) } @@ -1475,60 +1569,20 @@ export async function processWebhook( return new NextResponse('No messages in WhatsApp payload', { status: 200 }) } - // --- Send immediate acknowledgment and execute workflow asynchronously --- - logger.info( - `[${requestId}] Acknowledging webhook ${foundWebhook.id} and executing workflow ${foundWorkflow.id} asynchronously (Execution: ${executionId})` - ) - - // Execute workflow asynchronously without waiting for completion - executeWorkflowFromPayload( - foundWorkflow, - input, - executionId, - requestId, - foundWebhook.blockId - ).catch((error) => { - // Log any errors that occur during async execution - logger.error( - `[${requestId}] Error during async workflow execution for webhook ${foundWebhook.id} (Execution: ${executionId})`, - error - ) - }) - - // Return immediate acknowledgment to the webhook provider - // For Microsoft Teams outgoing webhooks, return the expected response format + // --- Route to appropriate processor based on provider --- if (foundWebhook.provider === 'microsoftteams') { - return NextResponse.json( - { - type: 'message', - text: 'Sim Studio', - }, - { status: 200 } + return await processMicrosoftTeamsWebhook( + foundWebhook, + foundWorkflow, + input, + executionId, + requestId ) } - return NextResponse.json({ message: 'Webhook received' }, { status: 200 }) + return await processStandardWebhook(foundWebhook, foundWorkflow, input, executionId, requestId) } catch (error: any) { - // Catch errors *before* calling executeWorkflowFromPayload (e.g., auth errors) - logger.error( - `[${requestId}] Error in processWebhook *before* execution for ${foundWebhook.id} (Execution: ${executionId})`, - error - ) - - // For Microsoft Teams outgoing webhooks, return the expected error format - if (foundWebhook.provider === 'microsoftteams') { - return NextResponse.json( - { - type: 'message', - text: 'Request received but processing failed', - }, - { status: 200 } - ) // Still return 200 to prevent Teams from showing additional error messages - } - - return new NextResponse(`Internal Server Error: ${error.message}`, { - status: 500, - }) + return handleWebhookError(error, foundWebhook, executionId, requestId) } }