fix(audit-log): address PR review — nullable workspaceId, enum usage, remove redundant queries

- Make audit_log.workspace_id nullable with ON DELETE SET NULL (logs survive workspace/user deletion)
- Make audit_log.actor_id nullable with ON DELETE SET NULL
- Replace all 53 routes' string literal action/resourceType with AuditAction.X and AuditResourceType.X enums
- Fix empty workspaceId ('') → null for OAuth, form, and org routes to avoid FK violations
- Remove redundant DB queries in chat manage route (use checkChatAccess return data)
- Fix organization routes to pass workspaceId: null instead of organizationId
This commit is contained in:
waleed
2026-02-17 23:21:18 -08:00
parent 0a2d89c049
commit ea6c27698d
65 changed files with 293 additions and 301 deletions

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, like, or } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
@@ -120,10 +120,10 @@ export async function POST(request: NextRequest) {
}
recordAudit({
workspaceId: '',
workspaceId: null,
actorId: session.user.id,
action: 'oauth.disconnected',
resourceType: 'oauth',
action: AuditAction.OAUTH_DISCONNECTED,
resourceType: AuditResourceType.OAUTH,
resourceId: providerId ?? provider,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -50,14 +50,7 @@ describe('Chat Edit API Route', () => {
}))
vi.doMock('@sim/db/schema', () => ({
chat: {
id: 'id',
identifier: 'identifier',
userId: 'userId',
workflowId: 'workflowId',
title: 'title',
},
workflow: { id: 'id', workspaceId: 'workspaceId' },
chat: { id: 'id', identifier: 'identifier', userId: 'userId' },
}))
// Mock logger - use loggerMock from @sim/testing
@@ -225,8 +218,11 @@ describe('Chat Edit API Route', () => {
workflowId: 'workflow-123',
}
mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat })
mockLimit.mockResolvedValueOnce([{ workspaceId: 'workspace-123' }])
mockCheckChatAccess.mockResolvedValue({
hasAccess: true,
chat: mockChat,
workspaceId: 'workspace-123',
})
const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', {
method: 'PATCH',
@@ -320,8 +316,11 @@ describe('Chat Edit API Route', () => {
workflowId: 'workflow-123',
}
mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat })
mockLimit.mockResolvedValueOnce([{ workspaceId: 'workspace-123' }])
mockCheckChatAccess.mockResolvedValue({
hasAccess: true,
chat: mockChat,
workspaceId: 'workspace-123',
})
const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', {
method: 'PATCH',
@@ -380,9 +379,11 @@ describe('Chat Edit API Route', () => {
}),
}))
mockCheckChatAccess.mockResolvedValue({ hasAccess: true })
mockLimit.mockResolvedValueOnce([{ workflowId: 'workflow-123', title: 'Test Chat' }])
mockLimit.mockResolvedValueOnce([{ workspaceId: 'workspace-123' }])
mockCheckChatAccess.mockResolvedValue({
hasAccess: true,
chat: { title: 'Test Chat', workflowId: 'workflow-123' },
workspaceId: 'workspace-123',
})
const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', {
method: 'DELETE',
@@ -403,9 +404,11 @@ describe('Chat Edit API Route', () => {
}),
}))
mockCheckChatAccess.mockResolvedValue({ hasAccess: true })
mockLimit.mockResolvedValueOnce([{ workflowId: 'workflow-123', title: 'Test Chat' }])
mockLimit.mockResolvedValueOnce([{ workspaceId: 'workspace-123' }])
mockCheckChatAccess.mockResolvedValue({
hasAccess: true,
chat: { title: 'Test Chat', workflowId: 'workflow-123' },
workspaceId: 'workspace-123',
})
const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', {
method: 'DELETE',

View File

@@ -1,10 +1,10 @@
import { db } from '@sim/db'
import { chat, workflow } from '@sim/db/schema'
import { chat } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { isDev } from '@/lib/core/config/feature-flags'
import { encryptSecret } from '@/lib/core/security/encryption'
@@ -104,7 +104,11 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
try {
const validatedData = chatUpdateSchema.parse(body)
const { hasAccess, chat: existingChatRecord } = await checkChatAccess(chatId, session.user.id)
const {
hasAccess,
chat: existingChatRecord,
workspaceId: chatWorkspaceId,
} = await checkChatAccess(chatId, session.user.id)
if (!hasAccess || !existingChatRecord) {
return createErrorResponse('Chat not found or access denied', 404)
@@ -218,19 +222,13 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
logger.info(`Chat "${chatId}" updated successfully`)
const [workflowRecord] = await db
.select({ workspaceId: workflow.workspaceId })
.from(workflow)
.where(eq(workflow.id, existingChat[0].workflowId))
.limit(1)
recordAudit({
workspaceId: workflowRecord?.workspaceId || '',
workspaceId: chatWorkspaceId || null,
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'chat.updated',
resourceType: 'chat',
action: AuditAction.CHAT_UPDATED,
resourceType: AuditResourceType.CHAT,
resourceId: chatId,
resourceName: title || existingChat[0].title,
description: `Updated chat deployment "${title || existingChat[0].title}"`,
@@ -272,37 +270,27 @@ export async function DELETE(
return createErrorResponse('Unauthorized', 401)
}
const { hasAccess } = await checkChatAccess(chatId, session.user.id)
const {
hasAccess,
chat: chatRecord,
workspaceId: chatWorkspaceId,
} = await checkChatAccess(chatId, session.user.id)
if (!hasAccess) {
return createErrorResponse('Chat not found or access denied', 404)
}
const [chatRecord] = await db
.select({ workflowId: chat.workflowId, title: chat.title })
.from(chat)
.where(eq(chat.id, chatId))
.limit(1)
const [workflowRecord] = chatRecord
? await db
.select({ workspaceId: workflow.workspaceId })
.from(workflow)
.where(eq(workflow.id, chatRecord.workflowId))
.limit(1)
: [undefined]
await db.delete(chat).where(eq(chat.id, chatId))
logger.info(`Chat "${chatId}" deleted successfully`)
recordAudit({
workspaceId: workflowRecord?.workspaceId || '',
workspaceId: chatWorkspaceId || null,
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'chat.deleted',
resourceType: 'chat',
action: AuditAction.CHAT_DELETED,
resourceType: AuditResourceType.CHAT,
resourceId: chatId,
resourceName: chatRecord?.title || chatId,
description: `Deleted chat deployment "${chatRecord?.title || chatId}"`,

View File

@@ -5,7 +5,7 @@ import { eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { isDev } from '@/lib/core/config/feature-flags'
import { encryptSecret } from '@/lib/core/security/encryption'
@@ -175,7 +175,7 @@ export async function POST(request: NextRequest) {
userId: session.user.id,
identifier,
title,
description: description || '',
description: description || null,
customizations: mergedCustomizations,
isActive: true,
authType,
@@ -226,12 +226,12 @@ export async function POST(request: NextRequest) {
}
recordAudit({
workspaceId: workflowRecord.workspaceId || '',
workspaceId: workflowRecord.workspaceId || null,
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'chat.deployed',
resourceType: 'chat',
action: AuditAction.CHAT_DEPLOYED,
resourceType: AuditResourceType.CHAT,
resourceId: id,
resourceName: title,
description: `Deployed chat "${title}"`,

View File

@@ -52,7 +52,7 @@ export async function checkWorkflowAccessForChatCreation(
export async function checkChatAccess(
chatId: string,
userId: string
): Promise<{ hasAccess: boolean; chat?: any }> {
): Promise<{ hasAccess: boolean; chat?: any; workspaceId?: string }> {
const chatData = await db
.select({
chat: chat,
@@ -78,7 +78,9 @@ export async function checkChatAccess(
action: 'admin',
})
return authorization.allowed ? { hasAccess: true, chat: chatRecord } : { hasAccess: false }
return authorization.allowed
? { hasAccess: true, chat: chatRecord, workspaceId: workflowWorkspaceId }
: { hasAccess: false }
}
export async function validateChatAuth(

View File

@@ -5,7 +5,7 @@ import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getEmailSubject, renderPollingGroupInvitationEmail } from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -179,8 +179,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: result.set.organizationId,
actorId: session.user.id,
action: 'credential_set_invitation.created',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_INVITATION_CREATED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -252,8 +252,8 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
recordAudit({
workspaceId: result.set.organizationId,
actorId: session.user.id,
action: 'credential_set_invitation.revoked',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_INVITATION_REVOKED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -3,7 +3,7 @@ import { account, credentialSet, credentialSetMember, member, user } from '@sim/
import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
@@ -181,8 +181,8 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
recordAudit({
workspaceId: result.set.organizationId,
actorId: session.user.id,
action: 'credential_set_member.removed',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_MEMBER_REMOVED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
@@ -135,8 +135,8 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: result.set.organizationId,
actorId: session.user.id,
action: 'credential_set.updated',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_UPDATED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -192,8 +192,8 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
recordAudit({
workspaceId: result.set.organizationId,
actorId: session.user.id,
action: 'credential_set.deleted',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_DELETED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, count, desc, eq } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasCredentialSetsAccess } from '@/lib/billing'
@@ -169,8 +169,8 @@ export async function POST(req: Request) {
recordAudit({
workspaceId: organizationId,
actorId: session.user.id,
action: 'credential_set.created',
resourceType: 'credential_set',
action: AuditAction.CREDENTIAL_SET_CREATED,
resourceType: AuditResourceType.CREDENTIAL_SET,
resourceId: newCredentialSet.id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate'
@@ -119,8 +119,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: targetWorkspaceId,
actorId: session.user.id,
action: 'folder.duplicated',
resourceType: 'folder',
action: AuditAction.FOLDER_DUPLICATED,
resourceType: AuditResourceType.FOLDER,
resourceId: newFolderId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -173,8 +173,8 @@ export async function DELETE(
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'folder.deleted',
resourceType: 'folder',
action: AuditAction.FOLDER_DELETED,
resourceType: AuditResourceType.FOLDER,
resourceId: id,
resourceName: existingFolder.name,
description: `Deleted folder "${existingFolder.name}"`,

View File

@@ -3,7 +3,7 @@ import { workflowFolder } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, asc, desc, eq, isNull } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -125,8 +125,8 @@ export async function POST(request: NextRequest) {
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'folder.created',
resourceType: 'folder',
action: AuditAction.FOLDER_CREATED,
resourceType: AuditResourceType.FOLDER,
resourceId: id,
resourceName: name.trim(),
description: `Created folder "${name.trim()}"`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { encryptSecret } from '@/lib/core/security/encryption'
import { checkFormAccess, DEFAULT_FORM_CUSTOMIZATIONS } from '@/app/api/form/utils'
@@ -186,10 +186,10 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
logger.info(`Form ${id} updated successfully`)
recordAudit({
workspaceId: '',
workspaceId: null,
actorId: session.user.id,
action: 'form.updated',
resourceType: 'form',
action: AuditAction.FORM_UPDATED,
resourceType: AuditResourceType.FORM,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -238,10 +238,10 @@ export async function DELETE(
logger.info(`Form ${id} deleted (soft delete)`)
recordAudit({
workspaceId: '',
workspaceId: null,
actorId: session.user.id,
action: 'form.deleted',
resourceType: 'form',
action: AuditAction.FORM_DELETED,
resourceType: AuditResourceType.FORM,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -5,7 +5,7 @@ import { eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { isDev } from '@/lib/core/config/feature-flags'
import { encryptSecret } from '@/lib/core/security/encryption'
@@ -179,7 +179,7 @@ export async function POST(request: NextRequest) {
userId: session.user.id,
identifier,
title,
description: description || '',
description: description || null,
customizations: mergedCustomizations,
isActive: true,
authType,
@@ -199,8 +199,8 @@ export async function POST(request: NextRequest) {
recordAudit({
workspaceId: workflowRecord.workspaceId ?? '',
actorId: session.user.id,
action: 'form.created',
resourceType: 'form',
action: AuditAction.FORM_CREATED,
resourceType: AuditResourceType.FORM,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import {
@@ -201,8 +201,8 @@ export async function PUT(
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? '',
actorId: userId,
action: 'document.updated',
resourceType: 'document',
action: AuditAction.DOCUMENT_UPDATED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: documentId,
resourceName: validatedData.filename ?? accessCheck.document?.filename,
description: `Updated document "${documentId}" in knowledge base "${knowledgeBaseId}"`,
@@ -272,8 +272,8 @@ export async function DELETE(
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? '',
actorId: userId,
action: 'document.deleted',
resourceType: 'document',
action: AuditAction.DOCUMENT_DELETED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: documentId,
resourceName: accessCheck.document?.filename,
description: `Deleted document "${documentId}" from knowledge base "${knowledgeBaseId}"`,

View File

@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import {
@@ -248,8 +248,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? '',
actorId: userId,
action: 'document.uploaded',
resourceType: 'document',
action: AuditAction.DOCUMENT_UPLOADED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: knowledgeBaseId,
resourceName: `${createdDocuments.length} document(s)`,
description: `Uploaded ${createdDocuments.length} document(s) to knowledge base "${knowledgeBaseId}"`,
@@ -307,8 +307,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: accessCheck.knowledgeBase?.workspaceId ?? '',
actorId: userId,
action: 'document.uploaded',
resourceType: 'document',
action: AuditAction.DOCUMENT_UPLOADED,
resourceType: AuditResourceType.DOCUMENT,
resourceId: knowledgeBaseId,
resourceName: validatedData.filename,
description: `Uploaded document "${validatedData.filename}" to knowledge base "${knowledgeBaseId}"`,

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -139,8 +139,8 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: accessCheck.knowledgeBase.workspaceId ?? '',
actorId: userId,
action: 'knowledge_base.updated',
resourceType: 'knowledge_base',
action: AuditAction.KNOWLEDGE_BASE_UPDATED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: id,
resourceName: validatedData.name ?? updatedKnowledgeBase.name,
description: `Updated knowledge base "${validatedData.name ?? updatedKnowledgeBase.name}"`,
@@ -212,8 +212,8 @@ export async function DELETE(
recordAudit({
workspaceId: accessCheck.knowledgeBase.workspaceId ?? '',
actorId: userId,
action: 'knowledge_base.deleted',
resourceType: 'knowledge_base',
action: AuditAction.KNOWLEDGE_BASE_DELETED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: id,
description: `Deleted knowledge base "${id}"`,
request: _request,

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -115,8 +115,8 @@ export async function POST(req: NextRequest) {
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'knowledge_base.created',
resourceType: 'knowledge_base',
action: AuditAction.KNOWLEDGE_BASE_CREATED,
resourceType: AuditResourceType.KNOWLEDGE_BASE,
resourceId: newKnowledgeBase.id,
resourceName: validatedData.name,
description: `Created knowledge base "${validatedData.name}"`,

View File

@@ -3,7 +3,7 @@ import { mcpServers } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { McpDomainNotAllowedError, validateMcpDomain } from '@/lib/mcp/domain-check'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpService } from '@/lib/mcp/service'
@@ -90,8 +90,8 @@ export const PATCH = withMcpAuth<{ id: string }>('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.updated',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
resourceName: updatedServer.name || serverId,
description: `Updated MCP server "${updatedServer.name || serverId}"`,

View File

@@ -3,7 +3,7 @@ import { mcpServers } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, isNull } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { McpDomainNotAllowedError, validateMcpDomain } from '@/lib/mcp/domain-check'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpService } from '@/lib/mcp/service'
@@ -165,8 +165,8 @@ export const POST = withMcpAuth('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.added',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_ADDED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
resourceName: body.name,
description: `Added MCP server "${body.name}"`,
@@ -225,8 +225,8 @@ export const DELETE = withMcpAuth('admin')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.removed',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_REMOVED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId!,
resourceName: deletedServer.name,
description: `Removed MCP server "${deletedServer.name}"`,

View File

@@ -3,7 +3,7 @@ import { workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -116,8 +116,8 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.updated',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
resourceName: updatedServer.name,
description: `Updated workflow MCP server "${updatedServer.name}"`,
@@ -164,8 +164,8 @@ export const DELETE = withMcpAuth<RouteParams>('admin')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.removed',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_REMOVED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
resourceName: deletedServer.name,
description: `Unpublished workflow MCP server "${deletedServer.name}"`,

View File

@@ -3,7 +3,7 @@ import { workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -122,8 +122,8 @@ export const PATCH = withMcpAuth<RouteParams>('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.updated',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
description: `Updated tool "${updatedTool.toolName}" in MCP server`,
metadata: { toolId, toolName: updatedTool.toolName },
@@ -180,8 +180,8 @@ export const DELETE = withMcpAuth<RouteParams>('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.updated',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
description: `Removed tool "${deletedTool.toolName}" from MCP server`,
metadata: { toolId, toolName: deletedTool.toolName },

View File

@@ -3,7 +3,7 @@ import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -201,8 +201,8 @@ export const POST = withMcpAuth<RouteParams>('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.updated',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_UPDATED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
description: `Added tool "${toolName}" to MCP server`,
metadata: { toolId, toolName, workflowId: body.workflowId },

View File

@@ -3,7 +3,7 @@ import { workflow, workflowMcpServer, workflowMcpTool } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { eq, inArray, sql } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware'
import { mcpPubSub } from '@/lib/mcp/pubsub'
import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils'
@@ -192,8 +192,8 @@ export const POST = withMcpAuth('write')(
recordAudit({
workspaceId,
actorId: userId,
action: 'mcp_server.added',
resourceType: 'mcp_server',
action: AuditAction.MCP_SERVER_ADDED,
resourceType: AuditResourceType.MCP_SERVER,
resourceId: serverId,
resourceName: body.name.trim(),
description: `Published workflow MCP server "${body.name.trim()}" with ${addedTools.length} tool(s)`,

View File

@@ -18,7 +18,7 @@ import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { getEmailSubject, renderInvitationEmail } from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasAccessControlAccess } from '@/lib/billing'
import { syncUsageLimitsFromSubscription } from '@/lib/billing/core/usage'
@@ -554,10 +554,10 @@ export async function PUT(
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: status === 'accepted' ? 'org_invitation.accepted' : 'org_invitation.updated',
resourceType: 'organization',
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -17,7 +17,7 @@ import {
renderBatchInvitationEmail,
renderInvitationEmail,
} from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import {
validateBulkInvitations,
@@ -414,10 +414,10 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
for (const inv of invitationsToCreate) {
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'org_invitation.created',
resourceType: 'organization',
action: AuditAction.ORG_INVITATION_CREATED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -550,10 +550,10 @@ export async function DELETE(
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'org_invitation.revoked',
resourceType: 'organization',
action: AuditAction.ORG_INVITATION_REVOKED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getUserUsageData } from '@/lib/billing/core/usage'
import { removeUserFromOrganization } from '@/lib/billing/organizations/membership'
@@ -215,10 +215,10 @@ export async function PUT(
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'org_member.role_changed',
resourceType: 'organization',
action: AuditAction.ORG_MEMBER_ROLE_CHANGED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -320,10 +320,10 @@ export async function DELETE(
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'org_member.removed',
resourceType: 'organization',
action: AuditAction.ORG_MEMBER_REMOVED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -5,7 +5,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { getEmailSubject, renderInvitationEmail } from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getUserUsageData } from '@/lib/billing/core/usage'
import { validateSeatAvailability } from '@/lib/billing/validation/seat-management'
@@ -287,10 +287,10 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'org_member.added',
resourceType: 'organization',
action: AuditAction.ORG_MEMBER_ADDED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, ne } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import {
getOrganizationSeatAnalytics,
@@ -194,10 +194,10 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'organization.updated',
resourceType: 'organization',
action: AuditAction.ORGANIZATION_UPDATED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -3,7 +3,7 @@ import { member, organization } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq, or } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { createOrganizationForTeamPlan } from '@/lib/billing/organization'
@@ -117,10 +117,10 @@ export async function POST(request: Request) {
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: user.id,
action: 'organization.created',
resourceType: 'organization',
action: AuditAction.ORGANIZATION_CREATED,
resourceType: AuditResourceType.ORGANIZATION,
resourceId: organizationId,
actorName: user.name ?? undefined,
actorEmail: user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasAccessControlAccess } from '@/lib/billing'
@@ -153,10 +153,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
})
recordAudit({
workspaceId: result.group.organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'permission_group_member.added',
resourceType: 'permission_group',
action: AuditAction.PERMISSION_GROUP_MEMBER_ADDED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -236,10 +236,10 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
})
recordAudit({
workspaceId: result.group.organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'permission_group_member.removed',
resourceType: 'permission_group',
action: AuditAction.PERMISSION_GROUP_MEMBER_REMOVED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasAccessControlAccess } from '@/lib/billing'
import {
@@ -183,10 +183,10 @@ export async function PUT(req: NextRequest, { params }: { params: Promise<{ id:
.limit(1)
recordAudit({
workspaceId: result.group.organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'permission_group.updated',
resourceType: 'permission_group',
action: AuditAction.PERMISSION_GROUP_UPDATED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -244,10 +244,10 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
logger.info('Deleted permission group', { permissionGroupId: id, userId: session.user.id })
recordAudit({
workspaceId: result.group.organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'permission_group.deleted',
resourceType: 'permission_group',
action: AuditAction.PERMISSION_GROUP_DELETED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, count, desc, eq } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasAccessControlAccess } from '@/lib/billing'
import {
@@ -200,10 +200,10 @@ export async function POST(req: Request) {
})
recordAudit({
workspaceId: organizationId,
workspaceId: null,
actorId: session.user.id,
action: 'permission_group.created',
resourceType: 'permission_group',
action: AuditAction.PERMISSION_GROUP_CREATED,
resourceType: AuditResourceType.PERMISSION_GROUP,
resourceId: newGroup.id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { validateCronExpression } from '@/lib/workflows/schedules/utils'
@@ -110,8 +110,8 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
recordAudit({
workspaceId: authorization.workflow.workspaceId ?? '',
actorId: session.user.id,
action: 'schedule.updated',
resourceType: 'schedule',
action: AuditAction.SCHEDULE_UPDATED,
resourceType: AuditResourceType.SCHEDULE,
resourceId: scheduleId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -3,7 +3,7 @@ import { apiKey } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -42,10 +42,10 @@ export async function DELETE(
}
recordAudit({
workspaceId: '',
workspaceId: null,
actorId: userId,
action: 'personal_api_key.revoked',
resourceType: 'api_key',
action: AuditAction.PERSONAL_API_KEY_REVOKED,
resourceType: AuditResourceType.API_KEY,
resourceId: keyId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -5,7 +5,7 @@ import { and, eq } from 'drizzle-orm'
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { createApiKey, getApiKeyDisplayFormat } from '@/lib/api-key/auth'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
const logger = createLogger('ApiKeysAPI')
@@ -112,10 +112,10 @@ export async function POST(request: NextRequest) {
})
recordAudit({
workspaceId: '',
workspaceId: null,
actorId: userId,
action: 'personal_api_key.created',
resourceType: 'api_key',
action: AuditAction.PERSONAL_API_KEY_CREATED,
resourceType: AuditResourceType.API_KEY,
resourceId: newKey.id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -3,7 +3,7 @@ import { webhook, workflow } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { validateInteger } from '@/lib/core/security/input-validation'
import { PlatformEvents } from '@/lib/core/telemetry'
@@ -263,10 +263,10 @@ export async function DELETE(
}
recordAudit({
workspaceId: webhookData.workflow.workspaceId || '',
workspaceId: webhookData.workflow.workspaceId || null,
actorId: userId,
action: 'webhook.deleted',
resourceType: 'webhook',
action: AuditAction.WEBHOOK_DELETED,
resourceType: AuditResourceType.WEBHOOK,
resourceId: id,
resourceName: foundWebhook.provider || 'generic',
description: 'Deleted webhook',

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, desc, eq, inArray, isNull, or } from 'drizzle-orm'
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -681,10 +681,10 @@ export async function POST(request: NextRequest) {
}
recordAudit({
workspaceId: workflowRecord.workspaceId || '',
workspaceId: workflowRecord.workspaceId || null,
actorId: userId,
action: 'webhook.created',
resourceType: 'webhook',
action: AuditAction.WEBHOOK_CREATED,
resourceType: AuditResourceType.WEBHOOK,
resourceId: savedWebhook.id,
resourceName: provider || 'generic',
description: `Created ${provider || 'generic'} webhook`,

View File

@@ -2,7 +2,7 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db'
import { createLogger } from '@sim/logger'
import { and, desc, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { generateRequestId } from '@/lib/core/utils/request'
import { removeMcpToolsForWorkflow, syncMcpToolsForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
import {
@@ -260,12 +260,12 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
await syncMcpToolsForWorkflow({ workflowId: id, requestId, context: 'deploy' })
recordAudit({
workspaceId: workflowData?.workspaceId || '',
workspaceId: workflowData?.workspaceId || null,
actorId: actorUserId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'workflow.deployed',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_DEPLOYED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: id,
resourceName: workflowData?.name,
description: `Deployed workflow "${workflowData?.name || id}"`,
@@ -340,12 +340,12 @@ export async function DELETE(
}
recordAudit({
workspaceId: workflowData?.workspaceId || '',
workspaceId: workflowData?.workspaceId || null,
actorId: session?.user?.id || '',
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'workflow.undeployed',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_UNDEPLOYED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: id,
resourceName: workflowData?.name,
description: `Undeployed workflow "${workflowData?.name || id}"`,

View File

@@ -2,7 +2,7 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import type { NextRequest } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { env } from '@/lib/core/config/env'
import { generateRequestId } from '@/lib/core/utils/request'
import { syncMcpToolsForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
@@ -115,8 +115,8 @@ export async function POST(
recordAudit({
workspaceId: workflowRecord?.workspaceId ?? '',
actorId: session!.user.id,
action: 'workflow.deployment_reverted',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_DEPLOYMENT_REVERTED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: id,
actorName: session!.user.name ?? undefined,
actorEmail: session!.user.email ?? undefined,

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -63,10 +63,10 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
)
recordAudit({
workspaceId: workspaceId || '',
workspaceId: workspaceId || null,
actorId: userId,
action: 'workflow.duplicated',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_DUPLICATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: result.id,
resourceName: result.name,
description: `Duplicated workflow from ${sourceWorkflowId}`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkHybridAuth, checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { env } from '@/lib/core/config/env'
import { PlatformEvents } from '@/lib/core/telemetry'
@@ -338,10 +338,10 @@ export async function DELETE(
}
recordAudit({
workspaceId: workflowData.workspaceId || '',
workspaceId: workflowData.workspaceId || null,
actorId: userId,
action: 'workflow.deleted',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_DELETED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,
resourceName: workflowData.name,
description: `Deleted workflow "${workflowData.name}"`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
@@ -83,8 +83,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
recordAudit({
workspaceId: workflowData.workspaceId ?? '',
actorId: userId,
action: 'workflow.variables_updated',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_VARIABLES_UPDATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,
resourceName: workflowData.name ?? undefined,
description: `Updated workflow variables`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, asc, eq, inArray, isNull, min } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
import { generateRequestId } from '@/lib/core/utils/request'
import { getUserEntityPermissions, workspaceExists } from '@/lib/workspaces/permissions/utils'
@@ -192,8 +192,8 @@ export async function POST(req: NextRequest) {
recordAudit({
workspaceId,
actorId: userId,
action: 'workflow.created',
resourceType: 'workflow',
action: AuditAction.WORKFLOW_CREATED,
resourceType: AuditResourceType.WORKFLOW,
resourceId: workflowId,
resourceName: name,
description: `Created workflow "${name}"`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, not } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -90,8 +90,8 @@ export async function PUT(
recordAudit({
workspaceId,
actorId: userId,
action: 'api_key.updated',
resourceType: 'api_key',
action: AuditAction.API_KEY_UPDATED,
resourceType: AuditResourceType.API_KEY,
resourceId: keyId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -146,8 +146,8 @@ export async function DELETE(
recordAudit({
workspaceId,
actorId: userId,
action: 'api_key.revoked',
resourceType: 'api_key',
action: AuditAction.API_KEY_REVOKED,
resourceType: AuditResourceType.API_KEY,
resourceId: keyId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -6,7 +6,7 @@ import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { createApiKey, getApiKeyDisplayFormat } from '@/lib/api-key/auth'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { PlatformEvents } from '@/lib/core/telemetry'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -165,8 +165,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'api_key.created',
resourceType: 'api_key',
action: AuditAction.API_KEY_CREATED,
resourceType: AuditResourceType.API_KEY,
resourceId: newKey.id,
resourceName: name,
description: `Created API key "${name}"`,
@@ -243,8 +243,8 @@ export async function DELETE(
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'api_key.revoked',
resourceType: 'api_key',
action: AuditAction.API_KEY_REVOKED,
resourceType: AuditResourceType.API_KEY,
description: `Revoked ${deletedCount} API key(s)`,
metadata: { keyIds: keys, deletedCount },
request,

View File

@@ -5,7 +5,7 @@ import { and, eq } from 'drizzle-orm'
import { nanoid } from 'nanoid'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -191,8 +191,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'byok_key.created',
resourceType: 'byok_key',
action: AuditAction.BYOK_KEY_CREATED,
resourceType: AuditResourceType.BYOK_KEY,
resourceId: newKey.id,
resourceName: providerId,
description: `Added BYOK key for ${providerId}`,
@@ -262,8 +262,8 @@ export async function DELETE(
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'byok_key.deleted',
resourceType: 'byok_key',
action: AuditAction.BYOK_KEY_DELETED,
resourceType: AuditResourceType.BYOK_KEY,
resourceName: providerId,
description: `Removed BYOK key for ${providerId}`,
metadata: { providerId },

View File

@@ -1,7 +1,7 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { duplicateWorkspace } from '@/lib/workspaces/duplicate'
@@ -51,8 +51,8 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id:
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'workspace.duplicated',
resourceType: 'workspace',
action: AuditAction.WORKSPACE_DUPLICATED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: result.id,
resourceName: name,
description: `Duplicated workspace to "${name}"`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption'
import { generateRequestId } from '@/lib/core/utils/request'
@@ -162,8 +162,8 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
actorId: userId,
actorName: session?.user?.name,
actorEmail: session?.user?.email,
action: 'environment.updated',
resourceType: 'environment',
action: AuditAction.ENVIRONMENT_UPDATED,
resourceType: AuditResourceType.ENVIRONMENT,
resourceId: workspaceId,
description: `Updated environment variables`,
metadata: { keysUpdated: Object.keys(variables) },

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { deleteWorkspaceFile } from '@/lib/uploads/contexts/workspace'
@@ -45,8 +45,8 @@ export async function DELETE(
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'file.deleted',
resourceType: 'file',
action: AuditAction.FILE_DELETED,
resourceType: AuditResourceType.FILE,
resourceId: fileId,
description: `Deleted file "${fileId}"`,
request,

View File

@@ -1,6 +1,6 @@
import { createLogger } from '@sim/logger'
import { type NextRequest, NextResponse } from 'next/server'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { generateRequestId } from '@/lib/core/utils/request'
import { listWorkspaceFiles, uploadWorkspaceFile } from '@/lib/uploads/contexts/workspace'
@@ -110,8 +110,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'file.uploaded',
resourceType: 'file',
action: AuditAction.FILE_UPLOADED,
resourceType: AuditResourceType.FILE,
resourceId: userFile.id,
resourceName: file.name,
description: `Uploaded file "${file.name}"`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { encryptSecret } from '@/lib/core/security/encryption'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -255,8 +255,8 @@ export async function PUT(request: NextRequest, { params }: RouteParams) {
recordAudit({
workspaceId,
actorId: session.user.id,
action: 'notification.updated',
resourceType: 'notification',
action: AuditAction.NOTIFICATION_UPDATED,
resourceType: AuditResourceType.NOTIFICATION,
resourceId: notificationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -327,8 +327,8 @@ export async function DELETE(request: NextRequest, { params }: RouteParams) {
recordAudit({
workspaceId,
actorId: session.user.id,
action: 'notification.deleted',
resourceType: 'notification',
action: AuditAction.NOTIFICATION_DELETED,
resourceType: AuditResourceType.NOTIFICATION,
resourceId: notificationId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -5,7 +5,7 @@ import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { v4 as uuidv4 } from 'uuid'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { encryptSecret } from '@/lib/core/security/encryption'
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
@@ -260,8 +260,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
recordAudit({
workspaceId,
actorId: session.user.id,
action: 'notification.created',
resourceType: 'notification',
action: AuditAction.NOTIFICATION_CREATED,
resourceType: AuditResourceType.NOTIFICATION,
resourceId: subscription.id,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -5,7 +5,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import {
getUsersWithPermissions,
@@ -161,8 +161,8 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
recordAudit({
workspaceId,
actorId: session.user.id,
action: 'member.role_changed',
resourceType: 'workspace',
action: AuditAction.MEMBER_ROLE_CHANGED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: workspaceId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -3,7 +3,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
const logger = createLogger('WorkspaceByIdAPI')
@@ -287,8 +287,8 @@ export async function DELETE(
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'workspace.deleted',
resourceType: 'workspace',
action: AuditAction.WORKSPACE_DELETED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: workspaceId,
description: 'Deleted workspace',
request,

View File

@@ -12,7 +12,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { WorkspaceInvitationEmail } from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { sendEmail } from '@/lib/messaging/email/mailer'
@@ -166,8 +166,8 @@ export async function GET(
recordAudit({
workspaceId: invitation.workspaceId,
actorId: session.user.id,
action: 'invitation.accepted',
resourceType: 'workspace',
action: AuditAction.INVITATION_ACCEPTED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: invitation.workspaceId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,
@@ -233,8 +233,8 @@ export async function DELETE(
recordAudit({
workspaceId: invitation.workspaceId,
actorId: session.user.id,
action: 'invitation.revoked',
resourceType: 'workspace',
action: AuditAction.INVITATION_REVOKED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: invitation.workspaceId,
actorName: session.user.name ?? undefined,
actorEmail: session.user.email ?? undefined,

View File

@@ -13,7 +13,7 @@ import { createLogger } from '@sim/logger'
import { and, eq, inArray } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { WorkspaceInvitationEmail } from '@/components/emails'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { PlatformEvents } from '@/lib/core/telemetry'
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -220,8 +220,8 @@ export async function POST(req: NextRequest) {
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'member.invited',
resourceType: 'workspace',
action: AuditAction.MEMBER_INVITED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: workspaceId,
resourceName: email,
description: `Invited ${email} as ${permission}`,

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { hasWorkspaceAdminAccess } from '@/lib/workspaces/permissions/utils'
@@ -107,8 +107,8 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'member.removed',
resourceType: 'workspace',
action: AuditAction.MEMBER_REMOVED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: workspaceId,
description: isSelf ? 'Left the workspace' : 'Removed a member from the workspace',
metadata: { removedUserId: userId, selfRemoval: isSelf },

View File

@@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger'
import { and, desc, eq, isNull } from 'drizzle-orm'
import { NextResponse } from 'next/server'
import { z } from 'zod'
import { recordAudit } from '@/lib/audit/log'
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
import { getSession } from '@/lib/auth'
import { PlatformEvents } from '@/lib/core/telemetry'
import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults'
@@ -74,8 +74,8 @@ export async function POST(req: Request) {
actorId: session.user.id,
actorName: session.user.name,
actorEmail: session.user.email,
action: 'workspace.created',
resourceType: 'workspace',
action: AuditAction.WORKSPACE_CREATED,
resourceType: AuditResourceType.WORKSPACE,
resourceId: newWorkspace.id,
resourceName: newWorkspace.name,
description: `Created workspace "${newWorkspace.name}"`,

View File

@@ -110,9 +110,12 @@ const logger = createLogger('McpSettings')
* Checks if a URL's hostname is in the allowed domains list.
* Returns true if no allowlist is configured (null) or the domain matches.
*/
const ENV_VAR_PATTERN = /\{\{[^}]+\}\}/
function isDomainAllowed(url: string | undefined, allowedDomains: string[] | null): boolean {
if (allowedDomains === null) return true
if (!url) return false
if (ENV_VAR_PATTERN.test(url)) return true
try {
const hostname = new URL(url).hostname.toLowerCase()
return allowedDomains.includes(hostname)

View File

@@ -151,7 +151,7 @@ export const AuditResourceType = {
export type AuditResourceTypeValue = (typeof AuditResourceType)[keyof typeof AuditResourceType]
interface AuditLogParams {
workspaceId: string
workspaceId?: string | null
actorId: string
action: AuditActionType
resourceType: AuditResourceTypeValue
@@ -177,7 +177,7 @@ export function recordAudit(params: AuditLogParams): void {
db.insert(auditLog)
.values({
id: nanoid(),
workspaceId: params.workspaceId,
workspaceId: params.workspaceId || null,
actorId: params.actorId,
action: params.action,
resourceType: params.resourceType,

View File

@@ -1,7 +1,7 @@
CREATE TABLE "audit_log" (
"id" text PRIMARY KEY NOT NULL,
"workspace_id" text NOT NULL,
"actor_id" text NOT NULL,
"workspace_id" text,
"actor_id" text,
"action" text NOT NULL,
"resource_type" text NOT NULL,
"resource_id" text,
@@ -15,8 +15,8 @@ CREATE TABLE "audit_log" (
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_actor_id_user_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "audit_log" ADD CONSTRAINT "audit_log_actor_id_user_id_fk" FOREIGN KEY ("actor_id") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "audit_log_workspace_created_idx" ON "audit_log" USING btree ("workspace_id","created_at");--> statement-breakpoint
CREATE INDEX "audit_log_actor_created_idx" ON "audit_log" USING btree ("actor_id","created_at");--> statement-breakpoint
CREATE INDEX "audit_log_resource_idx" ON "audit_log" USING btree ("resource_type","resource_id");--> statement-breakpoint

View File

@@ -1,5 +1,5 @@
{
"id": "a5703ff6-4481-4c0e-abc3-218e55a84376",
"id": "878a3e46-0446-46a9-9577-663699ae0373",
"prevId": "49f580f7-7eba-4431-bdf4-61db0e606546",
"version": "7",
"dialect": "postgresql",
@@ -977,13 +977,13 @@
"name": "workspace_id",
"type": "text",
"primaryKey": false,
"notNull": true
"notNull": false
},
"actor_id": {
"name": "actor_id",
"type": "text",
"primaryKey": false,
"notNull": true
"notNull": false
},
"action": {
"name": "action",
@@ -1141,7 +1141,7 @@
"tableTo": "workspace",
"columnsFrom": ["workspace_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onDelete": "set null",
"onUpdate": "no action"
},
"audit_log_actor_id_user_id_fk": {
@@ -1150,7 +1150,7 @@
"tableTo": "user",
"columnsFrom": ["actor_id"],
"columnsTo": ["id"],
"onDelete": "cascade",
"onDelete": "set null",
"onUpdate": "no action"
}
},

View File

@@ -1083,8 +1083,8 @@
{
"idx": 155,
"version": "7",
"when": 1771384748779,
"tag": "0155_lush_legion",
"when": 1771387813205,
"tag": "0155_ambiguous_blade",
"breakpoints": true
}
]

View File

@@ -2030,12 +2030,8 @@ export const auditLog = pgTable(
'audit_log',
{
id: text('id').primaryKey(),
workspaceId: text('workspace_id')
.notNull()
.references(() => workspace.id, { onDelete: 'cascade' }),
actorId: text('actor_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
workspaceId: text('workspace_id').references(() => workspace.id, { onDelete: 'set null' }),
actorId: text('actor_id').references(() => user.id, { onDelete: 'set null' }),
action: text('action').notNull(),
resourceType: text('resource_type').notNull(),
resourceId: text('resource_id'),