mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Merge pull request #789 from simstudioai/staging
v0.3.11: fix force-dynamic routes, webhooks, kb search perms
This commit is contained in:
@@ -9,6 +9,8 @@ import { member } from '@/db/schema'
|
||||
|
||||
const logger = createLogger('UnifiedBillingAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
/**
|
||||
* Unified Billing Endpoint
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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),
|
||||
])
|
||||
// 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 (kb.length === 0) {
|
||||
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)
|
||||
|
||||
if (missingKbIds.length > 0) {
|
||||
// Check if any requested knowledge bases were not accessible
|
||||
const inaccessibleKbIds = knowledgeBaseIds.filter((id) => !accessibleKbIds.includes(id))
|
||||
|
||||
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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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=<id>&organizationId=<id>
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<NextResponse> {
|
||||
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<NextResponse> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user