chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# Packages
|
||||
|
||||
## Internal
|
||||
|
||||
| Package | Description |
|
||||
|---------|-------------|
|
||||
| [@sim/tsconfig](./tsconfig) | Shared TypeScript configs (base, nextjs, library, library-build) |
|
||||
| [@sim/db](./db) | Database schema and Drizzle ORM utilities |
|
||||
| [@sim/logger](./logger) | Structured logging with colored output |
|
||||
| [@sim/testing](./testing) | Test factories, builders, and assertions |
|
||||
|
||||
## Published
|
||||
|
||||
| Package | npm | Description |
|
||||
|---------|-----|-------------|
|
||||
| [cli](./cli) | `simstudio` | Run Sim locally via Docker |
|
||||
| [ts-sdk](./ts-sdk) | `simstudio-ts-sdk` | TypeScript SDK for workflow execution |
|
||||
| [python-sdk](./python-sdk) | `simstudio-sdk` | Python SDK for workflow execution |
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@sim/audit",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"bun": ">=1.2.13",
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "biome check --write --unsafe .",
|
||||
"lint:check": "biome check .",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome format .",
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sim/db": "workspace:*",
|
||||
"@sim/logger": "workspace:*",
|
||||
"@sim/utils": "workspace:*",
|
||||
"drizzle-orm": "^0.45.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sim/testing": "workspace:*",
|
||||
"@sim/tsconfig": "workspace:*",
|
||||
"@types/node": "24.2.1",
|
||||
"typescript": "^7.0.2",
|
||||
"vitest": "^4.1.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { recordAudit } from './log'
|
||||
export type { AuditActionType, AuditResourceTypeValue } from './types'
|
||||
export { AuditAction, AuditResourceType } from './types'
|
||||
@@ -0,0 +1,414 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import {
|
||||
auditMock,
|
||||
dbChainMock,
|
||||
dbChainMockFns,
|
||||
requestUtilsMockFns,
|
||||
resetDbChainMock,
|
||||
} from '@sim/testing'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('@sim/db', () => ({
|
||||
...dbChainMock,
|
||||
auditLog: { id: 'id', workspaceId: 'workspace_id' },
|
||||
user: { id: 'id', name: 'name', email: 'email' },
|
||||
}))
|
||||
vi.mock('drizzle-orm', () => ({
|
||||
eq: vi.fn(),
|
||||
and: vi.fn(),
|
||||
or: vi.fn(),
|
||||
sql: vi.fn(),
|
||||
}))
|
||||
vi.mock('@sim/logger', () => ({
|
||||
createLogger: () => ({
|
||||
info: vi.fn(),
|
||||
warn: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
}),
|
||||
}))
|
||||
vi.mock('@sim/utils/id', () => ({
|
||||
generateId: () => 'test-uuid-123',
|
||||
generateShortId: () => 'test-id-123',
|
||||
isValidUuid: (v: string) =>
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v),
|
||||
}))
|
||||
|
||||
import { sleep } from '@sim/utils/helpers'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from './index'
|
||||
|
||||
const flush = () => sleep(10)
|
||||
|
||||
describe('AuditAction', () => {
|
||||
it('contains all expected action categories', () => {
|
||||
expect(AuditAction.WORKFLOW_CREATED).toBe('workflow.created')
|
||||
expect(AuditAction.MEMBER_INVITED).toBe('member.invited')
|
||||
expect(AuditAction.API_KEY_CREATED).toBe('api_key.created')
|
||||
expect(AuditAction.ORGANIZATION_CREATED).toBe('organization.created')
|
||||
})
|
||||
|
||||
it('has unique values for every key', () => {
|
||||
const values = Object.values(AuditAction)
|
||||
const unique = new Set(values)
|
||||
expect(unique.size).toBe(values.length)
|
||||
})
|
||||
})
|
||||
|
||||
describe('AuditResourceType', () => {
|
||||
it('contains all expected resource types', () => {
|
||||
expect(AuditResourceType.WORKFLOW).toBe('workflow')
|
||||
expect(AuditResourceType.WORKSPACE).toBe('workspace')
|
||||
expect(AuditResourceType.API_KEY).toBe('api_key')
|
||||
expect(AuditResourceType.MCP_SERVER).toBe('mcp_server')
|
||||
})
|
||||
|
||||
it('has unique values for every key', () => {
|
||||
const values = Object.values(AuditResourceType)
|
||||
const unique = new Set(values)
|
||||
expect(unique.size).toBe(values.length)
|
||||
})
|
||||
})
|
||||
|
||||
describe('recordAudit', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
resetDbChainMock()
|
||||
requestUtilsMockFns.mockGetClientIp.mockImplementation(
|
||||
(request: { headers: { get(name: string): string | null } }) =>
|
||||
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
||||
request.headers.get('x-real-ip')?.trim() ||
|
||||
'unknown'
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('inserts an audit log entry with all required fields', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test User',
|
||||
actorEmail: 'test@example.com',
|
||||
action: AuditAction.WORKFLOW_CREATED,
|
||||
resourceType: AuditResourceType.WORKFLOW,
|
||||
resourceId: 'wf-1',
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.insert).toHaveBeenCalledTimes(1)
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'test-id-123',
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
action: 'workflow.created',
|
||||
resourceType: 'workflow',
|
||||
resourceId: 'wf-1',
|
||||
metadata: {},
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('includes optional denormalized fields when provided', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
action: AuditAction.FOLDER_CREATED,
|
||||
resourceType: AuditResourceType.FOLDER,
|
||||
resourceId: 'folder-1',
|
||||
actorName: 'Waleed',
|
||||
actorEmail: 'waleed@example.com',
|
||||
resourceName: 'My Folder',
|
||||
description: 'Created folder "My Folder"',
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorName: 'Waleed',
|
||||
actorEmail: 'waleed@example.com',
|
||||
resourceName: 'My Folder',
|
||||
description: 'Created folder "My Folder"',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('extracts IP address from x-forwarded-for header', async () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: {
|
||||
'x-forwarded-for': '1.2.3.4, 5.6.7.8',
|
||||
'user-agent': 'TestAgent/1.0',
|
||||
},
|
||||
})
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.MEMBER_INVITED,
|
||||
resourceType: AuditResourceType.WORKSPACE,
|
||||
request,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ipAddress: '1.2.3.4',
|
||||
userAgent: 'TestAgent/1.0',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to x-real-ip when x-forwarded-for is absent', async () => {
|
||||
const request = new Request('https://example.com', {
|
||||
headers: { 'x-real-ip': '10.0.0.1' },
|
||||
})
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.API_KEY_CREATED,
|
||||
resourceType: AuditResourceType.API_KEY,
|
||||
request,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ipAddress: '10.0.0.1',
|
||||
userAgent: undefined,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('defaults metadata to empty object when not provided', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.ENVIRONMENT_UPDATED,
|
||||
resourceType: AuditResourceType.ENVIRONMENT,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(expect.objectContaining({ metadata: {} }))
|
||||
})
|
||||
|
||||
it('passes through metadata when provided', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.WEBHOOK_CREATED,
|
||||
resourceType: AuditResourceType.WEBHOOK,
|
||||
metadata: { provider: 'github', workflowId: 'wf-1' },
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
metadata: { provider: 'github', workflowId: 'wf-1' },
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('does not throw when the database insert fails', async () => {
|
||||
dbChainMockFns.values.mockImplementation(() => Promise.reject(new Error('DB connection lost')))
|
||||
|
||||
expect(() => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.WORKFLOW_DELETED,
|
||||
resourceType: AuditResourceType.WORKFLOW,
|
||||
})
|
||||
}).not.toThrow()
|
||||
|
||||
await flush()
|
||||
})
|
||||
|
||||
it('does not block — returns void synchronously', () => {
|
||||
const result = recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Test',
|
||||
actorEmail: 'test@test.com',
|
||||
action: AuditAction.CHAT_DEPLOYED,
|
||||
resourceType: AuditResourceType.CHAT,
|
||||
})
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
describe('lazy actor resolution', () => {
|
||||
it('looks up user when actorName and actorEmail are both undefined', async () => {
|
||||
dbChainMockFns.limit.mockResolvedValue([
|
||||
{ name: 'Resolved Name', email: 'resolved@example.com' },
|
||||
])
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
action: AuditAction.DOCUMENT_UPLOADED,
|
||||
resourceType: AuditResourceType.DOCUMENT,
|
||||
resourceId: 'doc-1',
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.select).toHaveBeenCalledTimes(1)
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorName: 'Resolved Name',
|
||||
actorEmail: 'resolved@example.com',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('skips lookup when actorName is provided (even if null)', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: null,
|
||||
actorEmail: null,
|
||||
action: AuditAction.DOCUMENT_UPLOADED,
|
||||
resourceType: AuditResourceType.DOCUMENT,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.select).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('skips lookup when actorName and actorEmail are provided', async () => {
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'user-1',
|
||||
actorName: 'Already Known',
|
||||
actorEmail: 'known@example.com',
|
||||
action: AuditAction.WORKFLOW_CREATED,
|
||||
resourceType: AuditResourceType.WORKFLOW,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.select).not.toHaveBeenCalled()
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorName: 'Already Known',
|
||||
actorEmail: 'known@example.com',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('nulls the actor FK when the lookup throws so the insert cannot FK-violate', async () => {
|
||||
dbChainMockFns.limit.mockRejectedValue(new Error('DB down'))
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'admin-api',
|
||||
action: AuditAction.KNOWLEDGE_BASE_CREATED,
|
||||
resourceType: AuditResourceType.KNOWLEDGE_BASE,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.select).toHaveBeenCalledTimes(1)
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorId: null,
|
||||
actorName: 'Admin API',
|
||||
actorEmail: undefined,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('nulls the actor FK and labels it System when the user is not found', async () => {
|
||||
dbChainMockFns.limit.mockResolvedValue([])
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'deleted-user',
|
||||
action: AuditAction.WORKFLOW_DELETED,
|
||||
resourceType: AuditResourceType.WORKFLOW,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.select).toHaveBeenCalledTimes(1)
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorId: null,
|
||||
actorName: 'System',
|
||||
actorEmail: undefined,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('labels the admin-api system actor while nulling its FK', async () => {
|
||||
dbChainMockFns.limit.mockResolvedValue([])
|
||||
|
||||
recordAudit({
|
||||
workspaceId: 'ws-1',
|
||||
actorId: 'admin-api',
|
||||
action: AuditAction.WORKFLOW_DELETED,
|
||||
resourceType: AuditResourceType.WORKFLOW,
|
||||
})
|
||||
|
||||
await flush()
|
||||
|
||||
expect(dbChainMockFns.values).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
actorId: null,
|
||||
actorName: 'Admin API',
|
||||
actorEmail: undefined,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('auditMock sync', () => {
|
||||
it('has the same AuditAction keys as the source', () => {
|
||||
const sourceKeys = Object.keys(AuditAction).sort()
|
||||
const mockKeys = Object.keys(auditMock.AuditAction).sort()
|
||||
expect(mockKeys).toEqual(sourceKeys)
|
||||
})
|
||||
|
||||
it('has the same AuditAction values as the source', () => {
|
||||
const mockActions = auditMock.AuditAction as Record<string, string>
|
||||
for (const key of Object.keys(AuditAction)) {
|
||||
expect(mockActions[key]).toBe(AuditAction[key as keyof typeof AuditAction])
|
||||
}
|
||||
})
|
||||
|
||||
it('has the same AuditResourceType keys as the source', () => {
|
||||
const sourceKeys = Object.keys(AuditResourceType).sort()
|
||||
const mockKeys = Object.keys(auditMock.AuditResourceType).sort()
|
||||
expect(mockKeys).toEqual(sourceKeys)
|
||||
})
|
||||
|
||||
it('has the same AuditResourceType values as the source', () => {
|
||||
const mockResourceTypes = auditMock.AuditResourceType as Record<string, string>
|
||||
for (const key of Object.keys(AuditResourceType)) {
|
||||
expect(mockResourceTypes[key]).toBe(AuditResourceType[key as keyof typeof AuditResourceType])
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,99 @@
|
||||
import { auditLog, db, user } from '@sim/db'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { generateShortId } from '@sim/utils/id'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import type { AuditActionType, AuditResourceTypeValue } from './types'
|
||||
|
||||
const logger = createLogger('AuditLog')
|
||||
|
||||
interface AuditLogParams {
|
||||
workspaceId?: string | null
|
||||
/**
|
||||
* The acting user's id (FK to `user.id`). Pass `null` for genuinely
|
||||
* actor-less events such as anonymous public-share access — the row is then
|
||||
* persisted with a null actor and the forensic context (ip/user-agent,
|
||||
* metadata) carries the trail instead.
|
||||
*/
|
||||
actorId: string | null
|
||||
action: AuditActionType
|
||||
resourceType: AuditResourceTypeValue
|
||||
resourceId?: string
|
||||
actorName?: string | null
|
||||
actorEmail?: string | null
|
||||
resourceName?: string
|
||||
description?: string
|
||||
metadata?: Record<string, unknown>
|
||||
request?: { headers: { get(name: string): string | null } }
|
||||
}
|
||||
|
||||
function getClientIp(request: { headers: { get(name: string): string | null } }): string {
|
||||
return (
|
||||
request.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||
|
||||
request.headers.get('x-real-ip')?.trim() ||
|
||||
'unknown'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire-and-forget audit log write. Never throws; failures are logged.
|
||||
* Resolves actorName/actorEmail from the user table when both are omitted.
|
||||
*/
|
||||
export function recordAudit(params: AuditLogParams): void {
|
||||
insertAuditLog(params).catch((error) => {
|
||||
logger.error('Failed to record audit log', { error, action: params.action })
|
||||
})
|
||||
}
|
||||
|
||||
async function insertAuditLog(params: AuditLogParams): Promise<void> {
|
||||
const ipAddress = params.request ? getClientIp(params.request) : undefined
|
||||
const userAgent = params.request?.headers.get('user-agent') ?? undefined
|
||||
|
||||
let { actorName, actorEmail } = params
|
||||
|
||||
/**
|
||||
* `actorId` is a FK to `user.id`. System actors (e.g. the shared `'admin-api'`
|
||||
* key) have no user row, so we persist a null FK with a readable label instead
|
||||
* of letting the insert fail. When the caller already supplies actorName/Email
|
||||
* we trust the id is a real user and skip the lookup.
|
||||
*/
|
||||
let actorId: string | null = params.actorId
|
||||
|
||||
if (actorName === undefined && actorEmail === undefined && actorId) {
|
||||
try {
|
||||
const [row] = await db
|
||||
.select({ name: user.name, email: user.email })
|
||||
.from(user)
|
||||
.where(eq(user.id, actorId))
|
||||
.limit(1)
|
||||
if (row) {
|
||||
actorName = row.name ?? undefined
|
||||
actorEmail = row.email ?? undefined
|
||||
} else {
|
||||
actorName = actorId === 'admin-api' ? 'Admin API' : 'System'
|
||||
actorId = null
|
||||
}
|
||||
} catch (error) {
|
||||
// Couldn't confirm the user exists — null the FK so the insert can't violate
|
||||
// it (system actor like 'admin-api', or a deleted user); the label remains.
|
||||
logger.warn('Failed to resolve actor info', { error, actorId })
|
||||
actorName = actorId === 'admin-api' ? 'Admin API' : 'System'
|
||||
actorId = null
|
||||
}
|
||||
}
|
||||
|
||||
await db.insert(auditLog).values({
|
||||
id: generateShortId(),
|
||||
workspaceId: params.workspaceId || null,
|
||||
actorId,
|
||||
action: params.action,
|
||||
resourceType: params.resourceType,
|
||||
resourceId: params.resourceId,
|
||||
actorName: actorName ?? undefined,
|
||||
actorEmail: actorEmail ?? undefined,
|
||||
resourceName: params.resourceName,
|
||||
description: params.description,
|
||||
metadata: params.metadata ?? {},
|
||||
ipAddress,
|
||||
userAgent,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* All auditable actions in the platform, grouped by resource type.
|
||||
*/
|
||||
export const AuditAction = {
|
||||
// API Keys
|
||||
API_KEY_CREATED: 'api_key.created',
|
||||
API_KEY_UPDATED: 'api_key.updated',
|
||||
API_KEY_REVOKED: 'api_key.revoked',
|
||||
PERSONAL_API_KEY_CREATED: 'personal_api_key.created',
|
||||
PERSONAL_API_KEY_REVOKED: 'personal_api_key.revoked',
|
||||
|
||||
// BYOK Keys
|
||||
BYOK_KEY_CREATED: 'byok_key.created',
|
||||
BYOK_KEY_UPDATED: 'byok_key.updated',
|
||||
BYOK_KEY_DELETED: 'byok_key.deleted',
|
||||
|
||||
// Chat
|
||||
CHAT_DEPLOYED: 'chat.deployed',
|
||||
CHAT_UPDATED: 'chat.updated',
|
||||
CHAT_DELETED: 'chat.deleted',
|
||||
|
||||
// Custom Blocks (deploy-as-block)
|
||||
CUSTOM_BLOCK_PUBLISHED: 'custom_block.published',
|
||||
CUSTOM_BLOCK_UPDATED: 'custom_block.updated',
|
||||
CUSTOM_BLOCK_DELETED: 'custom_block.deleted',
|
||||
|
||||
// Custom Tools
|
||||
CUSTOM_TOOL_CREATED: 'custom_tool.created',
|
||||
CUSTOM_TOOL_UPDATED: 'custom_tool.updated',
|
||||
CUSTOM_TOOL_DELETED: 'custom_tool.deleted',
|
||||
|
||||
// Data Drains
|
||||
DATA_DRAIN_CREATED: 'data_drain.created',
|
||||
DATA_DRAIN_UPDATED: 'data_drain.updated',
|
||||
DATA_DRAIN_DELETED: 'data_drain.deleted',
|
||||
DATA_DRAIN_RAN: 'data_drain.ran',
|
||||
DATA_DRAIN_TESTED: 'data_drain.tested',
|
||||
|
||||
// Billing
|
||||
CREDIT_PURCHASED: 'credit.purchased',
|
||||
CREDIT_ISSUED: 'credit.issued',
|
||||
INVOICE_PAYMENT_SUCCEEDED: 'invoice.payment_succeeded',
|
||||
INVOICE_PAYMENT_FAILED: 'invoice.payment_failed',
|
||||
OVERAGE_BILLED: 'billing.overage_billed',
|
||||
CHARGE_DISPUTE_OPENED: 'charge.dispute_opened',
|
||||
CHARGE_DISPUTE_CLOSED: 'charge.dispute_closed',
|
||||
|
||||
// Subscriptions
|
||||
SUBSCRIPTION_CREATED: 'subscription.created',
|
||||
SUBSCRIPTION_CANCELLED: 'subscription.cancelled',
|
||||
SUBSCRIPTION_TRANSFERRED: 'subscription.transferred',
|
||||
ENTERPRISE_SUBSCRIPTION_PROVISIONED: 'subscription.enterprise_provisioned',
|
||||
|
||||
// Connector Documents
|
||||
CONNECTOR_DOCUMENT_RESTORED: 'connector_document.restored',
|
||||
CONNECTOR_DOCUMENT_EXCLUDED: 'connector_document.excluded',
|
||||
|
||||
// Documents
|
||||
DOCUMENT_UPLOADED: 'document.uploaded',
|
||||
DOCUMENT_UPDATED: 'document.updated',
|
||||
DOCUMENT_DELETED: 'document.deleted',
|
||||
|
||||
// Environment
|
||||
ENVIRONMENT_UPDATED: 'environment.updated',
|
||||
ENVIRONMENT_DELETED: 'environment.deleted',
|
||||
|
||||
// Files
|
||||
FILE_UPLOADED: 'file.uploaded',
|
||||
FILE_UPDATED: 'file.updated',
|
||||
FILE_DELETED: 'file.deleted',
|
||||
FILE_RESTORED: 'file.restored',
|
||||
FILE_MOVED: 'file.moved',
|
||||
FILE_SHARED: 'file.shared',
|
||||
FILE_SHARE_DISABLED: 'file.share_disabled',
|
||||
FILE_DOWNLOADED: 'file.downloaded',
|
||||
|
||||
// Folders
|
||||
FOLDER_CREATED: 'folder.created',
|
||||
FOLDER_UPDATED: 'folder.updated',
|
||||
FOLDER_DELETED: 'folder.deleted',
|
||||
FOLDER_MOVED: 'folder.moved',
|
||||
FOLDER_DUPLICATED: 'folder.duplicated',
|
||||
FOLDER_RESTORED: 'folder.restored',
|
||||
|
||||
// Invitations
|
||||
INVITATION_ACCEPTED: 'invitation.accepted',
|
||||
INVITATION_REJECTED: 'invitation.rejected',
|
||||
INVITATION_RESENT: 'invitation.resent',
|
||||
INVITATION_REVOKED: 'invitation.revoked',
|
||||
INVITATION_UPDATED: 'invitation.updated',
|
||||
|
||||
// Knowledge Base Connectors
|
||||
CONNECTOR_CREATED: 'connector.created',
|
||||
CONNECTOR_UPDATED: 'connector.updated',
|
||||
CONNECTOR_DELETED: 'connector.deleted',
|
||||
CONNECTOR_SYNCED: 'connector.synced',
|
||||
|
||||
// Knowledge Bases
|
||||
KNOWLEDGE_BASE_CREATED: 'knowledge_base.created',
|
||||
KNOWLEDGE_BASE_UPDATED: 'knowledge_base.updated',
|
||||
KNOWLEDGE_BASE_DELETED: 'knowledge_base.deleted',
|
||||
KNOWLEDGE_BASE_RESTORED: 'knowledge_base.restored',
|
||||
|
||||
// MCP Servers
|
||||
MCP_SERVER_ADDED: 'mcp_server.added',
|
||||
MCP_SERVER_UPDATED: 'mcp_server.updated',
|
||||
MCP_SERVER_REMOVED: 'mcp_server.removed',
|
||||
|
||||
// Members
|
||||
MEMBER_INVITED: 'member.invited',
|
||||
MEMBER_ADDED: 'member.added',
|
||||
MEMBER_REMOVED: 'member.removed',
|
||||
MEMBER_ROLE_CHANGED: 'member.role_changed',
|
||||
|
||||
// OAuth / Credentials
|
||||
OAUTH_DISCONNECTED: 'oauth.disconnected',
|
||||
CREDENTIAL_CREATED: 'credential.created',
|
||||
CREDENTIAL_UPDATED: 'credential.updated',
|
||||
CREDENTIAL_RENAMED: 'credential.renamed',
|
||||
CREDENTIAL_RECONNECTED: 'credential.reconnected',
|
||||
CREDENTIAL_DELETED: 'credential.deleted',
|
||||
CREDENTIAL_ACCESSED: 'credential.accessed',
|
||||
CREDENTIAL_MEMBER_ADDED: 'credential_member.added',
|
||||
CREDENTIAL_MEMBER_REMOVED: 'credential_member.removed',
|
||||
CREDENTIAL_MEMBER_ROLE_CHANGED: 'credential_member.role_changed',
|
||||
|
||||
// Password
|
||||
PASSWORD_RESET_REQUESTED: 'password.reset_requested',
|
||||
PASSWORD_RESET: 'password.reset',
|
||||
|
||||
// Organizations
|
||||
ORGANIZATION_CREATED: 'organization.created',
|
||||
ORGANIZATION_UPDATED: 'organization.updated',
|
||||
ORG_MEMBER_ADDED: 'org_member.added',
|
||||
ORG_MEMBER_REMOVED: 'org_member.removed',
|
||||
ORG_MEMBER_ROLE_CHANGED: 'org_member.role_changed',
|
||||
ORG_MEMBER_USAGE_LIMIT_CHANGED: 'org_member.usage_limit_changed',
|
||||
ORG_INVITATION_CREATED: 'org_invitation.created',
|
||||
ORG_INVITATION_UPDATED: 'org_invitation.updated',
|
||||
ORG_INVITATION_ACCEPTED: 'org_invitation.accepted',
|
||||
ORG_INVITATION_REJECTED: 'org_invitation.rejected',
|
||||
ORG_INVITATION_CANCELLED: 'org_invitation.cancelled',
|
||||
ORG_INVITATION_REVOKED: 'org_invitation.revoked',
|
||||
ORG_INVITATION_RESENT: 'org_invitation.resent',
|
||||
ORG_SEAT_PROVISIONED: 'org_seat.provisioned',
|
||||
ORG_SEAT_DEPROVISIONED: 'org_seat.deprovisioned',
|
||||
ORG_PLAN_CONVERTED: 'org_plan.converted',
|
||||
|
||||
// Permission Groups
|
||||
PERMISSION_GROUP_CREATED: 'permission_group.created',
|
||||
PERMISSION_GROUP_UPDATED: 'permission_group.updated',
|
||||
PERMISSION_GROUP_DELETED: 'permission_group.deleted',
|
||||
PERMISSION_GROUP_MEMBER_ADDED: 'permission_group_member.added',
|
||||
PERMISSION_GROUP_MEMBER_REMOVED: 'permission_group_member.removed',
|
||||
|
||||
// Skills
|
||||
SKILL_CREATED: 'skill.created',
|
||||
SKILL_UPDATED: 'skill.updated',
|
||||
SKILL_DELETED: 'skill.deleted',
|
||||
|
||||
// Schedules
|
||||
SCHEDULE_CREATED: 'schedule.created',
|
||||
SCHEDULE_UPDATED: 'schedule.updated',
|
||||
SCHEDULE_DELETED: 'schedule.deleted',
|
||||
|
||||
// Tables
|
||||
TABLE_CREATED: 'table.created',
|
||||
TABLE_UPDATED: 'table.updated',
|
||||
TABLE_DELETED: 'table.deleted',
|
||||
TABLE_RESTORED: 'table.restored',
|
||||
TABLE_EXPORTED: 'table.exported',
|
||||
|
||||
// Webhooks
|
||||
WEBHOOK_CREATED: 'webhook.created',
|
||||
WEBHOOK_DELETED: 'webhook.deleted',
|
||||
|
||||
// Workflows
|
||||
WORKFLOW_CREATED: 'workflow.created',
|
||||
WORKFLOW_DELETED: 'workflow.deleted',
|
||||
WORKFLOW_RESTORED: 'workflow.restored',
|
||||
WORKFLOW_DEPLOYED: 'workflow.deployed',
|
||||
WORKFLOW_UNDEPLOYED: 'workflow.undeployed',
|
||||
WORKFLOW_DUPLICATED: 'workflow.duplicated',
|
||||
WORKFLOW_DEPLOYMENT_ACTIVATED: 'workflow.deployment_activated',
|
||||
WORKFLOW_DEPLOYMENT_REVERTED: 'workflow.deployment_reverted',
|
||||
WORKFLOW_LOCKED: 'workflow.locked',
|
||||
WORKFLOW_UNLOCKED: 'workflow.unlocked',
|
||||
WORKFLOW_VARIABLES_UPDATED: 'workflow.variables_updated',
|
||||
WORKFLOW_PUBLIC_API_TOGGLED: 'workflow.public_api_toggled',
|
||||
WORKFLOW_EXPORTED: 'workflow.exported',
|
||||
|
||||
// Workspaces
|
||||
WORKSPACE_CREATED: 'workspace.created',
|
||||
WORKSPACE_UPDATED: 'workspace.updated',
|
||||
WORKSPACE_DELETED: 'workspace.deleted',
|
||||
WORKSPACE_DUPLICATED: 'workspace.duplicated',
|
||||
WORKSPACE_FORKED: 'workspace.forked',
|
||||
WORKSPACE_FORK_PROMOTED: 'workspace.fork_promoted',
|
||||
WORKSPACE_FORK_ROLLED_BACK: 'workspace.fork_rolled_back',
|
||||
WORKSPACE_FORK_UNLINKED: 'workspace.fork_unlinked',
|
||||
WORKSPACE_EXPORTED: 'workspace.exported',
|
||||
} as const
|
||||
|
||||
export type AuditActionType = (typeof AuditAction)[keyof typeof AuditAction]
|
||||
|
||||
/**
|
||||
* All resource types that can appear in audit log entries.
|
||||
*/
|
||||
export const AuditResourceType = {
|
||||
API_KEY: 'api_key',
|
||||
BILLING: 'billing',
|
||||
BYOK_KEY: 'byok_key',
|
||||
CHAT: 'chat',
|
||||
CONNECTOR: 'connector',
|
||||
CREDENTIAL: 'credential',
|
||||
CUSTOM_BLOCK: 'custom_block',
|
||||
CUSTOM_TOOL: 'custom_tool',
|
||||
DATA_DRAIN: 'data_drain',
|
||||
DOCUMENT: 'document',
|
||||
ENVIRONMENT: 'environment',
|
||||
FILE: 'file',
|
||||
FOLDER: 'folder',
|
||||
KNOWLEDGE_BASE: 'knowledge_base',
|
||||
MCP_SERVER: 'mcp_server',
|
||||
OAUTH: 'oauth',
|
||||
ORGANIZATION: 'organization',
|
||||
PASSWORD: 'password',
|
||||
PERMISSION_GROUP: 'permission_group',
|
||||
SCHEDULE: 'schedule',
|
||||
SKILL: 'skill',
|
||||
SUBSCRIPTION: 'subscription',
|
||||
TABLE: 'table',
|
||||
WEBHOOK: 'webhook',
|
||||
WORKFLOW: 'workflow',
|
||||
WORKSPACE: 'workspace',
|
||||
} as const
|
||||
|
||||
export type AuditResourceTypeValue = (typeof AuditResourceType)[keyof typeof AuditResourceType]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@sim/tsconfig/library.json",
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
globals: false,
|
||||
environment: 'node',
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "@sim/auth",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"bun": ">=1.2.13",
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"exports": {
|
||||
"./verify": {
|
||||
"types": "./src/verify.ts",
|
||||
"default": "./src/verify.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "biome check --write --unsafe .",
|
||||
"lint:check": "biome check .",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome format ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@sim/db": "workspace:*",
|
||||
"better-auth": "1.6.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sim/tsconfig": "workspace:*",
|
||||
"@types/node": "24.2.1",
|
||||
"typescript": "^7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { db } from '@sim/db'
|
||||
import * as schema from '@sim/db/schema'
|
||||
import { betterAuth } from 'better-auth'
|
||||
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||
import { oneTimeToken } from 'better-auth/plugins'
|
||||
|
||||
export interface VerifyAuthOptions {
|
||||
/** Better Auth shared secret. Must match the apps/sim Better Auth secret. */
|
||||
secret: string
|
||||
/** Public-facing Better Auth URL (usually same as NEXT_PUBLIC_APP_URL). */
|
||||
baseURL: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal Better Auth instance used by services that only need to verify
|
||||
* one-time tokens issued by the main app. Shares the Better Auth DB schema
|
||||
* (`verification` table) and secret with the main app, so tokens issued by
|
||||
* `apps/sim`'s full auth config are accepted here.
|
||||
*/
|
||||
export function createVerifyAuth(options: VerifyAuthOptions) {
|
||||
return betterAuth({
|
||||
baseURL: options.baseURL,
|
||||
secret: options.secret,
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg',
|
||||
schema,
|
||||
}),
|
||||
plugins: [
|
||||
oneTimeToken({
|
||||
expiresIn: 24 * 60,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export type VerifyAuth = ReturnType<typeof createVerifyAuth>
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "@sim/tsconfig/library.json",
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# Sim CLI
|
||||
|
||||
Sim CLI allows you to run [Sim](https://sim.ai) using Docker with a single command.
|
||||
|
||||
## Installation
|
||||
|
||||
To install the Sim CLI globally, use:
|
||||
|
||||
```bash
|
||||
npm install -g simstudio
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To start Sim, simply run:
|
||||
|
||||
```bash
|
||||
simstudio
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `-p, --port <port>`: Specify the port to run Sim on (default: 3000).
|
||||
- `--no-pull`: Skip pulling the latest Docker images.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Docker must be installed and running on your machine.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please open an issue or submit a pull request.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the Apache-2.0 License.
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "simstudio",
|
||||
"version": "0.1.19",
|
||||
"description": "Sim CLI - Run Sim with a single command",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"bin": {
|
||||
"simstudio": "dist/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"type-check": "tsc --noEmit",
|
||||
"lint": "biome check --write --unsafe .",
|
||||
"lint:check": "biome check .",
|
||||
"format": "biome format --write .",
|
||||
"format:check": "biome format .",
|
||||
"prepublishOnly": "bun run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"simstudio",
|
||||
"ai",
|
||||
"workflow",
|
||||
"cli",
|
||||
"agent",
|
||||
"automation",
|
||||
"docker"
|
||||
],
|
||||
"author": "Sim",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^11.1.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"inquirer": "^8.2.6",
|
||||
"listr2": "^6.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sim/tsconfig": "workspace:*",
|
||||
"@types/inquirer": "^8.2.6",
|
||||
"@types/node": "^20.5.1",
|
||||
"typescript": "^7.0.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execSync, spawn } from 'child_process'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { createInterface } from 'readline'
|
||||
import chalk from 'chalk'
|
||||
import { Command } from 'commander'
|
||||
|
||||
const NETWORK_NAME = 'simstudio-network'
|
||||
const DB_CONTAINER = 'simstudio-db'
|
||||
const MIGRATIONS_CONTAINER = 'simstudio-migrations'
|
||||
const REALTIME_CONTAINER = 'simstudio-realtime'
|
||||
const APP_CONTAINER = 'simstudio-app'
|
||||
const DEFAULT_PORT = '3000'
|
||||
|
||||
const program = new Command()
|
||||
|
||||
program.name('simstudio').description('Run Sim using Docker').version('0.1.0')
|
||||
|
||||
program
|
||||
.option('-p, --port <port>', 'Port to run Sim on', DEFAULT_PORT)
|
||||
.option('-y, --yes', 'Skip interactive prompts and use defaults')
|
||||
.option('--no-pull', 'Skip pulling the latest Docker images')
|
||||
|
||||
function isDockerRunning(): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const docker = spawn('docker', ['info'])
|
||||
|
||||
docker.on('close', (code) => {
|
||||
resolve(code === 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function runCommand(command: string[]): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const process = spawn(command[0], command.slice(1), { stdio: 'inherit' })
|
||||
process.on('error', () => {
|
||||
resolve(false)
|
||||
})
|
||||
process.on('close', (code) => {
|
||||
resolve(code === 0)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async function ensureNetworkExists(): Promise<boolean> {
|
||||
try {
|
||||
const networks = execSync('docker network ls --format "{{.Name}}"').toString()
|
||||
if (!networks.includes(NETWORK_NAME)) {
|
||||
console.log(chalk.blue(`🔄 Creating Docker network '${NETWORK_NAME}'...`))
|
||||
return await runCommand(['docker', 'network', 'create', NETWORK_NAME])
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Failed to check networks:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function pullImage(image: string): Promise<boolean> {
|
||||
console.log(chalk.blue(`🔄 Pulling image ${image}...`))
|
||||
return await runCommand(['docker', 'pull', image])
|
||||
}
|
||||
|
||||
async function stopAndRemoveContainer(name: string): Promise<void> {
|
||||
try {
|
||||
execSync(`docker stop ${name} 2>/dev/null || true`)
|
||||
execSync(`docker rm ${name} 2>/dev/null || true`)
|
||||
} catch (_error) {
|
||||
// Ignore errors, container might not exist
|
||||
}
|
||||
}
|
||||
|
||||
async function cleanupExistingContainers(): Promise<void> {
|
||||
console.log(chalk.blue('🧹 Cleaning up any existing containers...'))
|
||||
await stopAndRemoveContainer(APP_CONTAINER)
|
||||
await stopAndRemoveContainer(DB_CONTAINER)
|
||||
await stopAndRemoveContainer(MIGRATIONS_CONTAINER)
|
||||
await stopAndRemoveContainer(REALTIME_CONTAINER)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = program.parse().opts()
|
||||
|
||||
console.log(chalk.blue('🚀 Starting Sim...'))
|
||||
|
||||
// Check if Docker is installed and running
|
||||
const dockerRunning = await isDockerRunning()
|
||||
if (!dockerRunning) {
|
||||
console.error(
|
||||
chalk.red('❌ Docker is not running or not installed. Please start Docker and try again.')
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Use port from options, with 3000 as default
|
||||
const port = options.port
|
||||
|
||||
// Pull latest images if not skipped
|
||||
if (options.pull) {
|
||||
await pullImage('ghcr.io/simstudioai/simstudio:latest')
|
||||
await pullImage('ghcr.io/simstudioai/migrations:latest')
|
||||
await pullImage('ghcr.io/simstudioai/realtime:latest')
|
||||
await pullImage('pgvector/pgvector:pg17')
|
||||
}
|
||||
|
||||
// Ensure Docker network exists
|
||||
if (!(await ensureNetworkExists())) {
|
||||
console.error(chalk.red('❌ Failed to create Docker network'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Clean up any existing containers
|
||||
await cleanupExistingContainers()
|
||||
|
||||
// Create data directory
|
||||
const dataDir = join(homedir(), '.simstudio', 'data')
|
||||
if (!existsSync(dataDir)) {
|
||||
try {
|
||||
mkdirSync(dataDir, { recursive: true })
|
||||
} catch (_error) {
|
||||
console.error(chalk.red(`❌ Failed to create data directory: ${dataDir}`))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Start PostgreSQL container
|
||||
console.log(chalk.blue('🔄 Starting PostgreSQL database...'))
|
||||
const dbSuccess = await runCommand([
|
||||
'docker',
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
DB_CONTAINER,
|
||||
'--network',
|
||||
NETWORK_NAME,
|
||||
'-e',
|
||||
'POSTGRES_USER=postgres',
|
||||
'-e',
|
||||
'POSTGRES_PASSWORD=postgres',
|
||||
'-e',
|
||||
'POSTGRES_DB=simstudio',
|
||||
'-v',
|
||||
`${dataDir}/postgres:/var/lib/postgresql/data`,
|
||||
'-p',
|
||||
'5432:5432',
|
||||
'pgvector/pgvector:pg17',
|
||||
])
|
||||
|
||||
if (!dbSuccess) {
|
||||
console.error(chalk.red('❌ Failed to start PostgreSQL'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Wait for PostgreSQL to be ready
|
||||
console.log(chalk.blue('⏳ Waiting for PostgreSQL to be ready...'))
|
||||
let pgReady = false
|
||||
for (let i = 0; i < 30; i++) {
|
||||
try {
|
||||
execSync(`docker exec ${DB_CONTAINER} pg_isready -U postgres`)
|
||||
pgReady = true
|
||||
break
|
||||
} catch (_error) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
|
||||
if (!pgReady) {
|
||||
console.error(chalk.red('❌ PostgreSQL failed to become ready'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
console.log(chalk.blue('🔄 Running database migrations...'))
|
||||
const migrationsSuccess = await runCommand([
|
||||
'docker',
|
||||
'run',
|
||||
'--rm',
|
||||
'--name',
|
||||
MIGRATIONS_CONTAINER,
|
||||
'--network',
|
||||
NETWORK_NAME,
|
||||
'-e',
|
||||
`DATABASE_URL=postgresql://postgres:postgres@${DB_CONTAINER}:5432/simstudio`,
|
||||
'ghcr.io/simstudioai/migrations:latest',
|
||||
'bun',
|
||||
'run',
|
||||
'db:migrate',
|
||||
])
|
||||
|
||||
if (!migrationsSuccess) {
|
||||
console.error(chalk.red('❌ Failed to run migrations'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Start the realtime server
|
||||
console.log(chalk.blue('🔄 Starting Realtime Server...'))
|
||||
const realtimeSuccess = await runCommand([
|
||||
'docker',
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
REALTIME_CONTAINER,
|
||||
'--network',
|
||||
NETWORK_NAME,
|
||||
'-p',
|
||||
'3002:3002',
|
||||
'-e',
|
||||
`DATABASE_URL=postgresql://postgres:postgres@${DB_CONTAINER}:5432/simstudio`,
|
||||
'-e',
|
||||
`BETTER_AUTH_URL=http://localhost:${port}`,
|
||||
'-e',
|
||||
`NEXT_PUBLIC_APP_URL=http://localhost:${port}`,
|
||||
'-e',
|
||||
'BETTER_AUTH_SECRET=your_auth_secret_here',
|
||||
'ghcr.io/simstudioai/realtime:latest',
|
||||
])
|
||||
|
||||
if (!realtimeSuccess) {
|
||||
console.error(chalk.red('❌ Failed to start Realtime Server'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Start the main application
|
||||
console.log(chalk.blue('🔄 Starting Sim...'))
|
||||
const appSuccess = await runCommand([
|
||||
'docker',
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
APP_CONTAINER,
|
||||
'--network',
|
||||
NETWORK_NAME,
|
||||
'-p',
|
||||
`${port}:3000`,
|
||||
'-e',
|
||||
`DATABASE_URL=postgresql://postgres:postgres@${DB_CONTAINER}:5432/simstudio`,
|
||||
'-e',
|
||||
`BETTER_AUTH_URL=http://localhost:${port}`,
|
||||
'-e',
|
||||
`NEXT_PUBLIC_APP_URL=http://localhost:${port}`,
|
||||
'-e',
|
||||
'BETTER_AUTH_SECRET=your_auth_secret_here',
|
||||
'-e',
|
||||
'ENCRYPTION_KEY=your_encryption_key_here',
|
||||
'ghcr.io/simstudioai/simstudio:latest',
|
||||
])
|
||||
|
||||
if (!appSuccess) {
|
||||
console.error(chalk.red('❌ Failed to start Sim'))
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(chalk.green(`✅ Sim is now running at ${chalk.bold(`http://localhost:${port}`)}`))
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`🛑 To stop all containers, run: ${chalk.bold('docker stop simstudio-app simstudio-db simstudio-realtime')}`
|
||||
)
|
||||
)
|
||||
|
||||
// Handle Ctrl+C
|
||||
const rl = createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
})
|
||||
|
||||
rl.on('SIGINT', async () => {
|
||||
console.log(chalk.yellow('\n🛑 Stopping Sim...'))
|
||||
|
||||
// Stop containers
|
||||
await stopAndRemoveContainer(APP_CONTAINER)
|
||||
await stopAndRemoveContainer(DB_CONTAINER)
|
||||
await stopAndRemoveContainer(REALTIME_CONTAINER)
|
||||
|
||||
console.log(chalk.green('✅ Sim has been stopped'))
|
||||
process.exit(0)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(chalk.red('❌ An error occurred:'), error)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@sim/tsconfig/library-build.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
# Database connection used by @sim/db scripts (drizzle-kit generate,
|
||||
# db:migrate, register-sso-provider, etc.). Must match DATABASE_URL in
|
||||
# apps/sim/.env and apps/realtime/.env. Migrations always run against the
|
||||
# primary — never set a replica URL here.
|
||||
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/simstudio"
|
||||
|
||||
# Direct (non-pooled) DSN for db:migrate. Required when DATABASE_URL points at
|
||||
# a transaction-pooling PgBouncer: session advisory locks and session SETs are
|
||||
# unsupported through transaction pooling. Falls back to DATABASE_URL.
|
||||
# MIGRATION_DATABASE_URL=""
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* @vitest-environment node
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { resolveDbUrl } from './connection-url'
|
||||
|
||||
describe('resolveDbUrl', () => {
|
||||
const KEYS = [
|
||||
'DATABASE_URL',
|
||||
'DATABASE_URL_WEB',
|
||||
'DATABASE_URL_TRIGGER',
|
||||
'DATABASE_REPLICA_URL',
|
||||
'DATABASE_REPLICA_URL_TRIGGER',
|
||||
] as const
|
||||
const saved: Record<string, string | undefined> = {}
|
||||
|
||||
beforeEach(() => {
|
||||
for (const key of KEYS) {
|
||||
saved[key] = process.env[key]
|
||||
delete process.env[key]
|
||||
}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
for (const key of KEYS) {
|
||||
if (saved[key] === undefined) delete process.env[key]
|
||||
else process.env[key] = saved[key]
|
||||
}
|
||||
})
|
||||
|
||||
it('prefers the role-keyed primary URL over the base', () => {
|
||||
process.env.DATABASE_URL = 'postgres://base/db'
|
||||
process.env.DATABASE_URL_TRIGGER = 'postgres://trigger/db'
|
||||
expect(resolveDbUrl('DATABASE_URL', 'trigger')).toBe('postgres://trigger/db')
|
||||
})
|
||||
|
||||
it('falls back to the base URL when the keyed var is unset', () => {
|
||||
process.env.DATABASE_URL = 'postgres://base/db'
|
||||
expect(resolveDbUrl('DATABASE_URL', 'web')).toBe('postgres://base/db')
|
||||
})
|
||||
|
||||
it('returns undefined when neither keyed nor base is set', () => {
|
||||
expect(resolveDbUrl('DATABASE_URL', 'realtime')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('resolves the replica variant independently of the primary', () => {
|
||||
process.env.DATABASE_REPLICA_URL = 'postgres://replica/db'
|
||||
process.env.DATABASE_REPLICA_URL_TRIGGER = 'postgres://trigger-replica/db'
|
||||
expect(resolveDbUrl('DATABASE_REPLICA_URL', 'trigger')).toBe('postgres://trigger-replica/db')
|
||||
expect(resolveDbUrl('DATABASE_REPLICA_URL', 'web')).toBe('postgres://replica/db')
|
||||
})
|
||||
|
||||
it('uppercases the role to build the keyed var name', () => {
|
||||
process.env.DATABASE_URL_WEB = 'postgres://web/db'
|
||||
expect(resolveDbUrl('DATABASE_URL', 'web')).toBe('postgres://web/db')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Resolve a connection URL for the active DB role, preferring the role-keyed
|
||||
* variant (e.g. `DATABASE_URL_TRIGGER`) and falling back to the shared base.
|
||||
* Lets each deploy point its surface at its own Postgres user + PgBouncer via
|
||||
* env alone; unset keyed vars preserve the prior single-URL behavior.
|
||||
*/
|
||||
export function resolveDbUrl(
|
||||
base: 'DATABASE_URL' | 'DATABASE_REPLICA_URL',
|
||||
role: string
|
||||
): string | undefined {
|
||||
return process.env[`${base}_${role.toUpperCase()}`] ?? process.env[base]
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Database-only constants used in schema definitions and migrations.
|
||||
* These constants are independent of application logic to keep migrations container lightweight.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default free credits (in dollars) for new users
|
||||
*/
|
||||
export const DEFAULT_FREE_CREDITS = 5
|
||||
|
||||
/**
|
||||
* Storage limit constants (in GB)
|
||||
* Can be overridden via environment variables
|
||||
*/
|
||||
export const DEFAULT_FREE_STORAGE_LIMIT_GB = 5
|
||||
export const DEFAULT_PRO_STORAGE_LIMIT_GB = 50
|
||||
export const DEFAULT_TEAM_STORAGE_LIMIT_GB = 500
|
||||
export const DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB = 500
|
||||
|
||||
/**
|
||||
* Text tag slots for knowledge base documents and embeddings
|
||||
*/
|
||||
export const TEXT_TAG_SLOTS = ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7'] as const
|
||||
|
||||
/**
|
||||
* Number tag slots for knowledge base documents and embeddings (5 slots)
|
||||
*/
|
||||
export const NUMBER_TAG_SLOTS = ['number1', 'number2', 'number3', 'number4', 'number5'] as const
|
||||
|
||||
/**
|
||||
* Date tag slots for knowledge base documents and embeddings (2 slots)
|
||||
*/
|
||||
export const DATE_TAG_SLOTS = ['date1', 'date2'] as const
|
||||
|
||||
/**
|
||||
* Boolean tag slots for knowledge base documents and embeddings (3 slots)
|
||||
*/
|
||||
export const BOOLEAN_TAG_SLOTS = ['boolean1', 'boolean2', 'boolean3'] as const
|
||||
|
||||
/**
|
||||
* All tag slots combined (for backwards compatibility)
|
||||
*/
|
||||
export const TAG_SLOTS = [
|
||||
...TEXT_TAG_SLOTS,
|
||||
...NUMBER_TAG_SLOTS,
|
||||
...DATE_TAG_SLOTS,
|
||||
...BOOLEAN_TAG_SLOTS,
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Type for all tag slot names
|
||||
*/
|
||||
export type TagSlot = (typeof TAG_SLOTS)[number]
|
||||
|
||||
/**
|
||||
* Type for text tag slot names
|
||||
*/
|
||||
export type TextTagSlot = (typeof TEXT_TAG_SLOTS)[number]
|
||||
|
||||
/**
|
||||
* Type for number tag slot names
|
||||
*/
|
||||
export type NumberTagSlot = (typeof NUMBER_TAG_SLOTS)[number]
|
||||
|
||||
/**
|
||||
* Type for date tag slot names
|
||||
*/
|
||||
export type DateTagSlot = (typeof DATE_TAG_SLOTS)[number]
|
||||
|
||||
/**
|
||||
* Type for boolean tag slot names
|
||||
*/
|
||||
export type BooleanTagSlot = (typeof BOOLEAN_TAG_SLOTS)[number]
|
||||
@@ -0,0 +1,73 @@
|
||||
import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import { resolveDbUrl } from './connection-url'
|
||||
import * as schema from './schema'
|
||||
import { instrumentPoolClient } from './tx-tripwire'
|
||||
|
||||
/**
|
||||
* Per-role pool profiles. Starting numbers — validate against real per-role
|
||||
* process counts (PgBouncer transaction mode, max_connections=200).
|
||||
*/
|
||||
export const DB_POOL_PROFILES = {
|
||||
web: { primaryMax: 10, replicaMax: 4, appName: 'sim-app' },
|
||||
// 5, not 3 — one run can need 3+ simultaneous connections (parallel queries +
|
||||
// overlapping logging writes); 3 risks intra-run deadlock.
|
||||
trigger: { primaryMax: 5, replicaMax: 2, appName: 'sim-trigger' },
|
||||
realtime: { primaryMax: 5, replicaMax: 3, appName: 'sim-realtime' },
|
||||
} as const
|
||||
|
||||
type DbRole = keyof typeof DB_POOL_PROFILES
|
||||
|
||||
const roleEnv = process.env.SIM_DB_ROLE?.trim()
|
||||
if (roleEnv && !Object.hasOwn(DB_POOL_PROFILES, roleEnv)) {
|
||||
throw new Error(
|
||||
`Invalid SIM_DB_ROLE '${roleEnv}' — expected one of ${Object.keys(DB_POOL_PROFILES).join(', ')} (or unset for web)`
|
||||
)
|
||||
}
|
||||
const role = (roleEnv as DbRole) || 'web'
|
||||
const profile = DB_POOL_PROFILES[role]
|
||||
|
||||
const connectionString = resolveDbUrl('DATABASE_URL', role)
|
||||
if (!connectionString) {
|
||||
throw new Error('Missing DATABASE_URL environment variable')
|
||||
}
|
||||
|
||||
const poolOptions = {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
connect_timeout: 30,
|
||||
onnotice: () => {},
|
||||
connection: { application_name: process.env.DB_APP_NAME ?? profile.appName },
|
||||
}
|
||||
|
||||
const postgresClient = instrumentPoolClient(
|
||||
postgres(connectionString, { ...poolOptions, max: profile.primaryMax }),
|
||||
'db'
|
||||
)
|
||||
|
||||
export const db = drizzle(postgresClient, { schema })
|
||||
|
||||
/**
|
||||
* Opt-in read-replica client for reads that tolerate bounded staleness and have
|
||||
* no read-your-writes dependency (logs, exports, dashboard aggregations). Never
|
||||
* for auth, workflow state, or billing enforcement. Falls back to the primary
|
||||
* when `DATABASE_REPLICA_URL` is unset, so call sites never branch.
|
||||
*/
|
||||
const replicaUrl = resolveDbUrl('DATABASE_REPLICA_URL', role)
|
||||
if (replicaUrl && !/^postgres(ql)?:\/\//.test(replicaUrl)) {
|
||||
throw new Error(
|
||||
'DATABASE_REPLICA_URL is set but is not a postgres:// DSN — fix the URL or unset the variable'
|
||||
)
|
||||
}
|
||||
|
||||
export const dbReplica: typeof db = replicaUrl
|
||||
? drizzle(
|
||||
instrumentPoolClient(
|
||||
postgres(replicaUrl, { ...poolOptions, max: profile.replicaMax }),
|
||||
'dbReplica'
|
||||
),
|
||||
{
|
||||
schema,
|
||||
}
|
||||
)
|
||||
: db
|
||||
@@ -0,0 +1,18 @@
|
||||
import { relative } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Config } from 'drizzle-kit'
|
||||
|
||||
const schemaPath = relative(process.cwd(), fileURLToPath(new URL('./schema.ts', import.meta.url)))
|
||||
const migrationsPath = relative(
|
||||
process.cwd(),
|
||||
fileURLToPath(new URL('./migrations', import.meta.url))
|
||||
)
|
||||
|
||||
export default {
|
||||
schema: schemaPath,
|
||||
out: migrationsPath,
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
} satisfies Config
|
||||
@@ -0,0 +1,5 @@
|
||||
export * from './connection-url'
|
||||
export * from './db'
|
||||
export * from './schema'
|
||||
export * from './triggers'
|
||||
export { instrumentPoolClient, runOutsideTransactionContext } from './tx-tripwire'
|
||||
@@ -0,0 +1,52 @@
|
||||
-- Current sql file was generated after introspecting the database
|
||||
|
||||
CREATE TABLE "verification" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"identifier" text NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"created_at" timestamp,
|
||||
"updated_at" timestamp
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"email_verified" boolean NOT NULL,
|
||||
"image" text,
|
||||
"created_at" timestamp NOT NULL,
|
||||
"updated_at" timestamp NOT NULL,
|
||||
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "account" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"account_id" text NOT NULL,
|
||||
"provider_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"access_token" text,
|
||||
"refresh_token" text,
|
||||
"id_token" text,
|
||||
"access_token_expires_at" timestamp,
|
||||
"refresh_token_expires_at" timestamp,
|
||||
"scope" text,
|
||||
"password" text,
|
||||
"created_at" timestamp NOT NULL,
|
||||
"updated_at" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "session" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"created_at" timestamp NOT NULL,
|
||||
"updated_at" timestamp NOT NULL,
|
||||
"ip_address" text,
|
||||
"user_agent" text,
|
||||
"user_id" text NOT NULL,
|
||||
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE "workflow" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"state" text NOT NULL,
|
||||
"last_synced" timestamp NOT NULL,
|
||||
"created_at" timestamp NOT NULL,
|
||||
"updated_at" timestamp NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD CONSTRAINT "workflow_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE "waitlist" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "waitlist_email_unique" UNIQUE("email")
|
||||
);
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE "logs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"execution_id" text,
|
||||
"level" text NOT NULL,
|
||||
"message" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_environment" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"variables" text NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_environment_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user_settings" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"is_auto_connect_enabled" boolean DEFAULT true NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_settings_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "logs" ADD CONSTRAINT "logs_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_environment" ADD CONSTRAINT "user_environment_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "user_settings" ADD COLUMN "is_debug_mode_enabled" boolean DEFAULT false NOT NULL;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "workflow_schedule" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"cron_expression" text,
|
||||
"next_run_at" timestamp,
|
||||
"last_ran_at" timestamp,
|
||||
"trigger_type" text NOT NULL,
|
||||
"timezone" text DEFAULT 'UTC' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow_schedule" ADD CONSTRAINT "workflow_schedule_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,35 @@
|
||||
CREATE TABLE "workflow_logs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"execution_id" text,
|
||||
"level" text NOT NULL,
|
||||
"message" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "settings" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"general" json NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "settings_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "environment" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"variables" json NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "environment_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "logs" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
ALTER TABLE "user_environment" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
ALTER TABLE "user_settings" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
DROP TABLE "logs" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "user_environment" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "user_settings" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ALTER COLUMN "state" SET DATA TYPE json USING state::json;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_logs" ADD CONSTRAINT "workflow_logs_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD CONSTRAINT "settings_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "environment" ADD CONSTRAINT "environment_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_schedule" ADD CONSTRAINT "workflow_schedule_workflow_id_unique" UNIQUE("workflow_id");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_logs" ADD COLUMN "duration" text;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "is_deployed" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "deployed_at" timestamp;--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "api_key" text;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_logs" ADD COLUMN "trigger" text NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_logs" ALTER COLUMN "trigger" DROP NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "color" text DEFAULT '#3972F6' NOT NULL;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "webhook" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"path" text NOT NULL,
|
||||
"secret" text,
|
||||
"provider" text,
|
||||
"is_active" boolean DEFAULT true NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "webhook" ADD CONSTRAINT "webhook_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "path_idx" ON "webhook" USING btree ("path");
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "webhook" ADD COLUMN "provider_config" json;--> statement-breakpoint
|
||||
ALTER TABLE "webhook" DROP COLUMN "secret";
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_logs" ADD COLUMN "metadata" json;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "collaborators" json DEFAULT '[]' NOT NULL;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "api_key" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"key" text NOT NULL,
|
||||
"last_used" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
"expires_at" timestamp,
|
||||
CONSTRAINT "api_key_key_unique" UNIQUE("key")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "api_key" ADD CONSTRAINT "api_key_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE "marketplace" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"state" json NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"author_id" text NOT NULL,
|
||||
"author_name" text NOT NULL,
|
||||
"stars" integer DEFAULT 0 NOT NULL,
|
||||
"executions" integer DEFAULT 0 NOT NULL,
|
||||
"category" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "marketplace_execution" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"marketplace_id" text NOT NULL,
|
||||
"user_id" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "marketplace_star" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"marketplace_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" ADD CONSTRAINT "marketplace_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" ADD CONSTRAINT "marketplace_author_id_user_id_fk" FOREIGN KEY ("author_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_execution" ADD CONSTRAINT "marketplace_execution_marketplace_id_marketplace_id_fk" FOREIGN KEY ("marketplace_id") REFERENCES "public"."marketplace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_execution" ADD CONSTRAINT "marketplace_execution_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_star" ADD CONSTRAINT "marketplace_star_marketplace_id_marketplace_id_fk" FOREIGN KEY ("marketplace_id") REFERENCES "public"."marketplace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace_star" ADD CONSTRAINT "marketplace_star_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_marketplace_idx" ON "marketplace_star" USING btree ("user_id","marketplace_id");
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE "marketplace_execution" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" RENAME COLUMN "executions" TO "views";
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "is_published" boolean DEFAULT false NOT NULL;
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE "user_stats" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"total_manual_executions" integer DEFAULT 0 NOT NULL,
|
||||
"total_api_calls" integer DEFAULT 0 NOT NULL,
|
||||
"total_webhook_triggers" integer DEFAULT 0 NOT NULL,
|
||||
"total_scheduled_executions" integer DEFAULT 0 NOT NULL,
|
||||
"total_tokens_used" integer DEFAULT 0 NOT NULL,
|
||||
"total_cost" numeric DEFAULT '0' NOT NULL,
|
||||
"last_active" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_stats_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "run_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "last_run_at" timestamp;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD CONSTRAINT "user_stats_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "variables" json DEFAULT '{}';
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" DROP COLUMN "api_key";
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "marketplace_data" json DEFAULT 'null'::json;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ALTER COLUMN "marketplace_data" DROP DEFAULT;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "deployed_state" json;
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE "marketplace_star" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "marketplace" DROP COLUMN "stars";
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE "custom_tools" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"schema" json NOT NULL,
|
||||
"code" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "custom_tools" ADD CONSTRAINT "custom_tools_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,35 @@
|
||||
CREATE TABLE "chat" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"subdomain" text NOT NULL,
|
||||
"title" text NOT NULL,
|
||||
"description" text,
|
||||
"is_active" boolean DEFAULT true NOT NULL,
|
||||
"customizations" json DEFAULT '{}',
|
||||
"auth_type" text DEFAULT 'public' NOT NULL,
|
||||
"password" text,
|
||||
"allowed_emails" json DEFAULT '[]',
|
||||
"output_block_id" text,
|
||||
"output_path" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "subscription" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"plan" text NOT NULL,
|
||||
"reference_id" text NOT NULL,
|
||||
"stripe_customer_id" text,
|
||||
"stripe_subscription_id" text,
|
||||
"status" text,
|
||||
"period_start" timestamp,
|
||||
"period_end" timestamp,
|
||||
"cancel_at_period_end" boolean,
|
||||
"seats" integer
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user" ADD COLUMN "stripe_customer_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "chat" ADD CONSTRAINT "chat_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "chat" ADD CONSTRAINT "chat_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "subdomain_idx" ON "chat" USING btree ("subdomain");
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE TABLE "invitation" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"inviter_id" text NOT NULL,
|
||||
"organization_id" text NOT NULL,
|
||||
"role" text NOT NULL,
|
||||
"status" text NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "member" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"organization_id" text NOT NULL,
|
||||
"role" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "organization" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"slug" text NOT NULL,
|
||||
"logo" text,
|
||||
"metadata" jsonb,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD COLUMN "active_organization_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD COLUMN "trial_start" timestamp;--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD COLUMN "trial_end" timestamp;--> statement-breakpoint
|
||||
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "session" ADD CONSTRAINT "session_active_organization_id_organization_id_fk" FOREIGN KEY ("active_organization_id") REFERENCES "public"."organization"("id") ON DELETE set null ON UPDATE no action;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "chat" ADD COLUMN "output_configs" json DEFAULT '[]';--> statement-breakpoint
|
||||
ALTER TABLE "chat" DROP COLUMN "output_block_id";--> statement-breakpoint
|
||||
ALTER TABLE "chat" DROP COLUMN "output_path";
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE "settings" ALTER COLUMN "general" SET DEFAULT '{}';--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "theme" text DEFAULT 'system' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "debug_mode" boolean DEFAULT false NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "auto_connect" boolean DEFAULT true NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "auto_fill_env_vars" boolean DEFAULT true NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "telemetry_enabled" boolean DEFAULT true NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" ADD COLUMN "telemetry_notified_user" boolean DEFAULT false NOT NULL;
|
||||
@@ -0,0 +1,23 @@
|
||||
CREATE TABLE "workspace" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"owner_id" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workspace_member" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workspace_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"role" text DEFAULT 'member' NOT NULL,
|
||||
"joined_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "workspace_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "workspace" ADD CONSTRAINT "workspace_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workspace_member" ADD CONSTRAINT "workspace_member_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workspace_member" ADD CONSTRAINT "workspace_member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_workspace_idx" ON "workspace_member" USING btree ("user_id","workspace_id");--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD CONSTRAINT "workflow_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE "workspace_invitation" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workspace_id" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"inviter_id" text NOT NULL,
|
||||
"role" text DEFAULT 'member' NOT NULL,
|
||||
"status" text DEFAULT 'pending' NOT NULL,
|
||||
"token" text NOT NULL,
|
||||
"expires_at" timestamp NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "workspace_invitation_token_unique" UNIQUE("token")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workspace_invitation" ADD CONSTRAINT "workspace_invitation_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workspace_invitation" ADD CONSTRAINT "workspace_invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "organization" ALTER COLUMN "metadata" SET DATA TYPE json;--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD COLUMN "metadata" json;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_chat_executions" integer DEFAULT 0 NOT NULL;
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "workflow_schedule" ADD COLUMN "failed_count" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_schedule" ADD COLUMN "status" text DEFAULT 'active' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_schedule" ADD COLUMN "last_failed_at" timestamp;
|
||||
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE "memory" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text,
|
||||
"key" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"data" json NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
"deleted_at" timestamp
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "memory" ADD CONSTRAINT "memory_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "memory_key_idx" ON "memory" USING btree ("key");--> statement-breakpoint
|
||||
CREATE INDEX "memory_workflow_idx" ON "memory" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "memory_workflow_key_idx" ON "memory" USING btree ("workflow_id","key");
|
||||
@@ -0,0 +1,114 @@
|
||||
-- Enable pgvector extension
|
||||
CREATE EXTENSION IF NOT EXISTS vector;
|
||||
|
||||
-- Create knowledge_base table
|
||||
CREATE TABLE IF NOT EXISTS "knowledge_base" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workspace_id" text,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"token_count" integer DEFAULT 0 NOT NULL,
|
||||
"embedding_model" text DEFAULT 'text-embedding-3-small' NOT NULL,
|
||||
"embedding_dimension" integer DEFAULT 1536 NOT NULL,
|
||||
"chunking_config" json DEFAULT '{"maxSize": 1024, "minSize": 100, "overlap": 200}' NOT NULL,
|
||||
"deleted_at" timestamp,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Create document table
|
||||
CREATE TABLE IF NOT EXISTS "document" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"knowledge_base_id" text NOT NULL,
|
||||
"filename" text NOT NULL,
|
||||
"file_url" text NOT NULL,
|
||||
"file_size" integer NOT NULL,
|
||||
"mime_type" text NOT NULL,
|
||||
"file_hash" text,
|
||||
"chunk_count" integer DEFAULT 0 NOT NULL,
|
||||
"token_count" integer DEFAULT 0 NOT NULL,
|
||||
"character_count" integer DEFAULT 0 NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"deleted_at" timestamp,
|
||||
"uploaded_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- Create embedding table with optimized vector type
|
||||
CREATE TABLE IF NOT EXISTS "embedding" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"knowledge_base_id" text NOT NULL,
|
||||
"document_id" text NOT NULL,
|
||||
"chunk_index" integer NOT NULL,
|
||||
"chunk_hash" text NOT NULL,
|
||||
"content" text NOT NULL,
|
||||
"content_length" integer NOT NULL,
|
||||
"token_count" integer NOT NULL,
|
||||
"embedding" vector(1536) NOT NULL, -- Optimized for text-embedding-3-small with HNSW support
|
||||
"embedding_model" text DEFAULT 'text-embedding-3-small' NOT NULL,
|
||||
"start_offset" integer NOT NULL,
|
||||
"end_offset" integer NOT NULL,
|
||||
"overlap_tokens" integer DEFAULT 0 NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"search_rank" numeric DEFAULT '1.0',
|
||||
"access_count" integer DEFAULT 0 NOT NULL,
|
||||
"last_accessed_at" timestamp,
|
||||
"quality_score" numeric,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
|
||||
-- Ensure embedding exists (simplified constraint)
|
||||
CONSTRAINT "embedding_not_null_check" CHECK ("embedding" IS NOT NULL)
|
||||
);
|
||||
|
||||
-- Add foreign key constraints
|
||||
ALTER TABLE "knowledge_base" ADD CONSTRAINT "knowledge_base_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "knowledge_base" ADD CONSTRAINT "knowledge_base_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "workspace"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "document" ADD CONSTRAINT "document_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "knowledge_base"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "embedding" ADD CONSTRAINT "embedding_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "knowledge_base"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "embedding" ADD CONSTRAINT "embedding_document_id_document_id_fk" FOREIGN KEY ("document_id") REFERENCES "document"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
-- Create indexes for knowledge_base table
|
||||
CREATE INDEX IF NOT EXISTS "kb_user_id_idx" ON "knowledge_base" USING btree ("user_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_workspace_id_idx" ON "knowledge_base" USING btree ("workspace_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_user_workspace_idx" ON "knowledge_base" USING btree ("user_id","workspace_id");
|
||||
CREATE INDEX IF NOT EXISTS "kb_deleted_at_idx" ON "knowledge_base" USING btree ("deleted_at");
|
||||
|
||||
-- Create indexes for document table
|
||||
CREATE INDEX IF NOT EXISTS "doc_kb_id_idx" ON "document" USING btree ("knowledge_base_id");
|
||||
CREATE INDEX IF NOT EXISTS "doc_file_hash_idx" ON "document" USING btree ("file_hash");
|
||||
CREATE INDEX IF NOT EXISTS "doc_filename_idx" ON "document" USING btree ("filename");
|
||||
CREATE INDEX IF NOT EXISTS "doc_kb_uploaded_at_idx" ON "document" USING btree ("knowledge_base_id","uploaded_at");
|
||||
|
||||
-- Create embedding table indexes
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_id_idx" ON "embedding" USING btree ("knowledge_base_id");
|
||||
CREATE INDEX IF NOT EXISTS "emb_doc_id_idx" ON "embedding" USING btree ("document_id");
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "emb_doc_chunk_idx" ON "embedding" USING btree ("document_id","chunk_index");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_model_idx" ON "embedding" USING btree ("knowledge_base_id","embedding_model");
|
||||
CREATE INDEX IF NOT EXISTS "emb_chunk_hash_idx" ON "embedding" USING btree ("chunk_hash");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_access_idx" ON "embedding" USING btree ("knowledge_base_id","last_accessed_at");
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_rank_idx" ON "embedding" USING btree ("knowledge_base_id","search_rank");
|
||||
|
||||
-- Create optimized HNSW index for vector similarity search
|
||||
CREATE INDEX IF NOT EXISTS "embedding_vector_hnsw_idx" ON "embedding"
|
||||
USING hnsw ("embedding" vector_cosine_ops)
|
||||
WITH (m = 16, ef_construction = 64);
|
||||
|
||||
-- GIN index for JSONB metadata queries
|
||||
CREATE INDEX IF NOT EXISTS "emb_metadata_gin_idx" ON "embedding" USING gin ("metadata");
|
||||
|
||||
-- Full-text search support with generated tsvector column
|
||||
ALTER TABLE "embedding" ADD COLUMN IF NOT EXISTS "content_tsv" tsvector GENERATED ALWAYS AS (to_tsvector('english', "content")) STORED;
|
||||
CREATE INDEX IF NOT EXISTS "emb_content_fts_idx" ON "embedding" USING gin ("content_tsv");
|
||||
|
||||
-- Performance optimization: Set fillfactor for high-update tables
|
||||
ALTER TABLE "embedding" SET (fillfactor = 85);
|
||||
ALTER TABLE "document" SET (fillfactor = 90);
|
||||
|
||||
-- Add table comments for documentation
|
||||
COMMENT ON TABLE "knowledge_base" IS 'Stores knowledge base configurations and settings';
|
||||
COMMENT ON TABLE "document" IS 'Stores document metadata and processing status';
|
||||
COMMENT ON TABLE "embedding" IS 'Stores vector embeddings optimized for text-embedding-3-small with HNSW similarity search';
|
||||
COMMENT ON COLUMN "embedding"."embedding" IS 'Vector embedding using pgvector type optimized for HNSW similarity search';
|
||||
COMMENT ON COLUMN "embedding"."metadata" IS 'JSONB metadata for flexible filtering (e.g., page numbers, sections, tags)';
|
||||
COMMENT ON COLUMN "embedding"."search_rank" IS 'Boost factor for search results, higher values appear first';
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Add enabled field to embedding table
|
||||
ALTER TABLE "embedding" ADD COLUMN IF NOT EXISTS "enabled" boolean DEFAULT true NOT NULL;
|
||||
|
||||
-- Composite index for knowledge base + enabled chunks (for search optimization)
|
||||
CREATE INDEX IF NOT EXISTS "emb_kb_enabled_idx" ON "embedding" USING btree ("knowledge_base_id", "enabled");
|
||||
|
||||
-- Composite index for document + enabled chunks (for document chunk listings)
|
||||
CREATE INDEX IF NOT EXISTS "emb_doc_enabled_idx" ON "embedding" USING btree ("document_id", "enabled");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "settings" ADD COLUMN "email_preferences" json DEFAULT '{}' NOT NULL;
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE "workflow_folder" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workspace_id" text NOT NULL,
|
||||
"parent_id" text,
|
||||
"color" text DEFAULT '#6B7280',
|
||||
"is_expanded" boolean DEFAULT true NOT NULL,
|
||||
"sort_order" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD COLUMN "folder_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_folder" ADD CONSTRAINT "workflow_folder_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_folder" ADD CONSTRAINT "workflow_folder_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_folder" ADD CONSTRAINT "workflow_folder_parent_id_workflow_folder_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."workflow_folder"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "workflow_folder_workspace_parent_idx" ON "workflow_folder" USING btree ("workspace_id","parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_folder_user_idx" ON "workflow_folder" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_folder_parent_sort_idx" ON "workflow_folder" USING btree ("parent_id","sort_order");--> statement-breakpoint
|
||||
ALTER TABLE "workflow" ADD CONSTRAINT "workflow_folder_id_workflow_folder_id_fk" FOREIGN KEY ("folder_id") REFERENCES "public"."workflow_folder"("id") ON DELETE set null ON UPDATE no action;
|
||||
@@ -0,0 +1,58 @@
|
||||
CREATE TABLE "workflow_blocks" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"position_x" integer NOT NULL,
|
||||
"position_y" integer NOT NULL,
|
||||
"enabled" boolean DEFAULT true NOT NULL,
|
||||
"horizontal_handles" boolean DEFAULT true NOT NULL,
|
||||
"is_wide" boolean DEFAULT false NOT NULL,
|
||||
"height" integer DEFAULT 0 NOT NULL,
|
||||
"sub_blocks" jsonb DEFAULT '{}' NOT NULL,
|
||||
"outputs" jsonb DEFAULT '{}' NOT NULL,
|
||||
"data" jsonb DEFAULT '{}',
|
||||
"parent_id" text,
|
||||
"extent" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workflow_edges" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"source_block_id" text NOT NULL,
|
||||
"target_block_id" text NOT NULL,
|
||||
"source_handle" text,
|
||||
"target_handle" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workflow_subflows" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"type" text NOT NULL,
|
||||
"config" jsonb DEFAULT '{}' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow_blocks" ADD CONSTRAINT "workflow_blocks_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_blocks" ADD CONSTRAINT "workflow_blocks_parent_id_workflow_blocks_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_edges" ADD CONSTRAINT "workflow_edges_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_edges" ADD CONSTRAINT "workflow_edges_source_block_id_workflow_blocks_id_fk" FOREIGN KEY ("source_block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_edges" ADD CONSTRAINT "workflow_edges_target_block_id_workflow_blocks_id_fk" FOREIGN KEY ("target_block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_subflows" ADD CONSTRAINT "workflow_subflows_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "workflow_blocks_workflow_id_idx" ON "workflow_blocks" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_blocks_parent_id_idx" ON "workflow_blocks" USING btree ("parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_blocks_workflow_parent_idx" ON "workflow_blocks" USING btree ("workflow_id","parent_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_blocks_workflow_type_idx" ON "workflow_blocks" USING btree ("workflow_id","type");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_workflow_id_idx" ON "workflow_edges" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_source_block_idx" ON "workflow_edges" USING btree ("source_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_target_block_idx" ON "workflow_edges" USING btree ("target_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_workflow_source_idx" ON "workflow_edges" USING btree ("workflow_id","source_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_workflow_target_idx" ON "workflow_edges" USING btree ("workflow_id","target_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_source_block_fk_idx" ON "workflow_edges" USING btree ("source_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_edges_target_block_fk_idx" ON "workflow_edges" USING btree ("target_block_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_subflows_workflow_id_idx" ON "workflow_subflows" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_subflows_workflow_type_idx" ON "workflow_subflows" USING btree ("workflow_id","type");
|
||||
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "workflow_blocks" ALTER COLUMN "position_x" SET DATA TYPE numeric;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_blocks" ALTER COLUMN "position_y" SET DATA TYPE numeric;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_blocks" ALTER COLUMN "height" SET DATA TYPE numeric;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_blocks" ALTER COLUMN "height" SET DEFAULT '0';
|
||||
@@ -0,0 +1,10 @@
|
||||
DROP INDEX "doc_file_hash_idx";--> statement-breakpoint
|
||||
DROP INDEX "emb_chunk_hash_idx";--> statement-breakpoint
|
||||
DROP INDEX "emb_kb_access_idx";--> statement-breakpoint
|
||||
DROP INDEX "emb_kb_rank_idx";--> statement-breakpoint
|
||||
ALTER TABLE "document" DROP COLUMN "file_hash";--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "overlap_tokens";--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "search_rank";--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "access_count";--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "last_accessed_at";--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "quality_score";
|
||||
@@ -0,0 +1,19 @@
|
||||
CREATE TYPE "public"."permission_type" AS ENUM('admin', 'write', 'read');--> statement-breakpoint
|
||||
CREATE TABLE "permissions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"entity_type" text NOT NULL,
|
||||
"entity_id" text NOT NULL,
|
||||
"permission_type" "permission_type" NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workspace_invitation" ADD COLUMN "permissions" "permission_type" DEFAULT 'admin' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "permissions" ADD CONSTRAINT "permissions_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "permissions_user_id_idx" ON "permissions" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "permissions_entity_idx" ON "permissions" USING btree ("entity_type","entity_id");--> statement-breakpoint
|
||||
CREATE INDEX "permissions_user_entity_type_idx" ON "permissions" USING btree ("user_id","entity_type");--> statement-breakpoint
|
||||
CREATE INDEX "permissions_user_entity_permission_idx" ON "permissions" USING btree ("user_id","entity_type","permission_type");--> statement-breakpoint
|
||||
CREATE INDEX "permissions_user_entity_idx" ON "permissions" USING btree ("user_id","entity_type","entity_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "permissions_unique_constraint" ON "permissions" USING btree ("user_id","entity_type","entity_id");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_blocks" ADD COLUMN "advanced_mode" boolean DEFAULT false NOT NULL;
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE "user_stats" ADD COLUMN "current_usage_limit" numeric DEFAULT '5' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "usage_limit_set_by" text;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "usage_limit_updated_at" timestamp DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "current_period_cost" numeric DEFAULT '0' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "billing_period_start" timestamp DEFAULT now();--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "billing_period_end" timestamp;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "last_period_cost" numeric DEFAULT '0';--> statement-breakpoint
|
||||
CREATE INDEX "subscription_reference_status_idx" ON "subscription" USING btree ("reference_id","status");--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD CONSTRAINT "check_enterprise_metadata" CHECK (plan != 'enterprise' OR (metadata IS NOT NULL AND (metadata->>'perSeatAllowance' IS NOT NULL OR metadata->>'totalAllowance' IS NOT NULL)));
|
||||
@@ -0,0 +1,82 @@
|
||||
CREATE TABLE "workflow_execution_blocks" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"execution_id" text NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"block_id" text NOT NULL,
|
||||
"block_name" text,
|
||||
"block_type" text NOT NULL,
|
||||
"started_at" timestamp NOT NULL,
|
||||
"ended_at" timestamp,
|
||||
"duration_ms" integer,
|
||||
"status" text NOT NULL,
|
||||
"error_message" text,
|
||||
"error_stack_trace" text,
|
||||
"input_data" jsonb,
|
||||
"output_data" jsonb,
|
||||
"cost_input" numeric(10, 6),
|
||||
"cost_output" numeric(10, 6),
|
||||
"cost_total" numeric(10, 6),
|
||||
"tokens_prompt" integer,
|
||||
"tokens_completion" integer,
|
||||
"tokens_total" integer,
|
||||
"model_used" text,
|
||||
"metadata" jsonb,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workflow_execution_logs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"execution_id" text NOT NULL,
|
||||
"state_snapshot_id" text NOT NULL,
|
||||
"level" text NOT NULL,
|
||||
"message" text NOT NULL,
|
||||
"trigger" text NOT NULL,
|
||||
"started_at" timestamp NOT NULL,
|
||||
"ended_at" timestamp,
|
||||
"total_duration_ms" integer,
|
||||
"block_count" integer DEFAULT 0 NOT NULL,
|
||||
"success_count" integer DEFAULT 0 NOT NULL,
|
||||
"error_count" integer DEFAULT 0 NOT NULL,
|
||||
"skipped_count" integer DEFAULT 0 NOT NULL,
|
||||
"total_cost" numeric(10, 6),
|
||||
"total_input_cost" numeric(10, 6),
|
||||
"total_output_cost" numeric(10, 6),
|
||||
"total_tokens" integer,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "workflow_execution_snapshots" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"state_hash" text NOT NULL,
|
||||
"state_data" jsonb NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "workflow_execution_blocks" ADD CONSTRAINT "workflow_execution_blocks_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_execution_logs" ADD CONSTRAINT "workflow_execution_logs_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_execution_logs" ADD CONSTRAINT "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk" FOREIGN KEY ("state_snapshot_id") REFERENCES "public"."workflow_execution_snapshots"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_execution_snapshots" ADD CONSTRAINT "workflow_execution_snapshots_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_execution_id_idx" ON "workflow_execution_blocks" USING btree ("execution_id");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_workflow_id_idx" ON "workflow_execution_blocks" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_block_id_idx" ON "workflow_execution_blocks" USING btree ("block_id");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_status_idx" ON "workflow_execution_blocks" USING btree ("status");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_duration_idx" ON "workflow_execution_blocks" USING btree ("duration_ms");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_cost_idx" ON "workflow_execution_blocks" USING btree ("cost_total");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_workflow_execution_idx" ON "workflow_execution_blocks" USING btree ("workflow_id","execution_id");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_execution_status_idx" ON "workflow_execution_blocks" USING btree ("execution_id","status");--> statement-breakpoint
|
||||
CREATE INDEX "execution_blocks_started_at_idx" ON "workflow_execution_blocks" USING btree ("started_at");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_workflow_id_idx" ON "workflow_execution_logs" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_execution_id_idx" ON "workflow_execution_logs" USING btree ("execution_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_trigger_idx" ON "workflow_execution_logs" USING btree ("trigger");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_level_idx" ON "workflow_execution_logs" USING btree ("level");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_started_at_idx" ON "workflow_execution_logs" USING btree ("started_at");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_cost_idx" ON "workflow_execution_logs" USING btree ("total_cost");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_duration_idx" ON "workflow_execution_logs" USING btree ("total_duration_ms");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "workflow_execution_logs_execution_id_unique" ON "workflow_execution_logs" USING btree ("execution_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_snapshots_workflow_id_idx" ON "workflow_execution_snapshots" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_snapshots_hash_idx" ON "workflow_execution_snapshots" USING btree ("state_hash");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "workflow_snapshots_workflow_hash_idx" ON "workflow_execution_snapshots" USING btree ("workflow_id","state_hash");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_snapshots_created_at_idx" ON "workflow_execution_snapshots" USING btree ("created_at");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "settings" ADD COLUMN "auto_pan" boolean DEFAULT true NOT NULL;
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE TABLE "docs_embeddings" (
|
||||
"chunk_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"chunk_text" text NOT NULL,
|
||||
"source_document" text NOT NULL,
|
||||
"source_link" text NOT NULL,
|
||||
"header_text" text NOT NULL,
|
||||
"header_level" integer NOT NULL,
|
||||
"token_count" integer NOT NULL,
|
||||
"embedding" vector(1536) NOT NULL,
|
||||
"embedding_model" text DEFAULT 'text-embedding-3-small' NOT NULL,
|
||||
"metadata" jsonb DEFAULT '{}' NOT NULL,
|
||||
"chunk_text_tsv" "tsvector" GENERATED ALWAYS AS (to_tsvector('english', "docs_embeddings"."chunk_text")) STORED,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "docs_embedding_not_null_check" CHECK ("embedding" IS NOT NULL),
|
||||
CONSTRAINT "docs_header_level_check" CHECK ("header_level" >= 1 AND "header_level" <= 6)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_source_document_idx" ON "docs_embeddings" USING btree ("source_document");--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_header_level_idx" ON "docs_embeddings" USING btree ("header_level");--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_source_header_idx" ON "docs_embeddings" USING btree ("source_document","header_level");--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_model_idx" ON "docs_embeddings" USING btree ("embedding_model");--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_created_at_idx" ON "docs_embeddings" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "docs_embedding_vector_hnsw_idx" ON "docs_embeddings" USING hnsw ("embedding" vector_cosine_ops) WITH (m=16,ef_construction=64);--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_metadata_gin_idx" ON "docs_embeddings" USING gin ("metadata");--> statement-breakpoint
|
||||
CREATE INDEX "docs_emb_chunk_text_fts_idx" ON "docs_embeddings" USING gin ("chunk_text_tsv");--> statement-breakpoint
|
||||
CREATE OR REPLACE FUNCTION trigger_set_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;--> statement-breakpoint
|
||||
CREATE TRIGGER set_updated_at
|
||||
BEFORE UPDATE ON docs_embeddings
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION trigger_set_timestamp();
|
||||
@@ -0,0 +1,18 @@
|
||||
CREATE TABLE "copilot_chats" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"title" text,
|
||||
"messages" jsonb DEFAULT '[]' NOT NULL,
|
||||
"model" text DEFAULT 'claude-3-7-sonnet-latest' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "copilot_chats" ADD CONSTRAINT "copilot_chats_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_chats" ADD CONSTRAINT "copilot_chats_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "copilot_chats_user_id_idx" ON "copilot_chats" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_chats_workflow_id_idx" ON "copilot_chats" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_chats_user_workflow_idx" ON "copilot_chats" USING btree ("user_id","workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_chats_created_at_idx" ON "copilot_chats" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_chats_updated_at_idx" ON "copilot_chats" USING btree ("updated_at");
|
||||
@@ -0,0 +1,30 @@
|
||||
DROP INDEX "emb_metadata_gin_idx";--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag1" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag2" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag3" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag4" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag5" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag6" text;--> statement-breakpoint
|
||||
ALTER TABLE "document" ADD COLUMN "tag7" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag1" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag2" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag3" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag4" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag5" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag6" text;--> statement-breakpoint
|
||||
ALTER TABLE "embedding" ADD COLUMN "tag7" text;--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag1_idx" ON "document" USING btree ("tag1");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag2_idx" ON "document" USING btree ("tag2");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag3_idx" ON "document" USING btree ("tag3");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag4_idx" ON "document" USING btree ("tag4");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag5_idx" ON "document" USING btree ("tag5");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag6_idx" ON "document" USING btree ("tag6");--> statement-breakpoint
|
||||
CREATE INDEX "doc_tag7_idx" ON "document" USING btree ("tag7");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag1_idx" ON "embedding" USING btree ("tag1");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag2_idx" ON "embedding" USING btree ("tag2");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag3_idx" ON "embedding" USING btree ("tag3");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag4_idx" ON "embedding" USING btree ("tag4");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag5_idx" ON "embedding" USING btree ("tag5");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag6_idx" ON "embedding" USING btree ("tag6");--> statement-breakpoint
|
||||
CREATE INDEX "emb_tag7_idx" ON "embedding" USING btree ("tag7");--> statement-breakpoint
|
||||
ALTER TABLE "embedding" DROP COLUMN "metadata";
|
||||
@@ -0,0 +1,47 @@
|
||||
CREATE TABLE "template_stars" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"template_id" text NOT NULL,
|
||||
"starred_at" timestamp DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "templates" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"author" text NOT NULL,
|
||||
"views" integer DEFAULT 0 NOT NULL,
|
||||
"stars" integer DEFAULT 0 NOT NULL,
|
||||
"color" text DEFAULT '#3972F6' NOT NULL,
|
||||
"icon" text DEFAULT 'FileText' NOT NULL,
|
||||
"category" text NOT NULL,
|
||||
"state" jsonb NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP TABLE "workspace_member" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "template_stars" ADD CONSTRAINT "template_stars_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "template_stars" ADD CONSTRAINT "template_stars_template_id_templates_id_fk" FOREIGN KEY ("template_id") REFERENCES "public"."templates"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "templates" ADD CONSTRAINT "templates_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "templates" ADD CONSTRAINT "templates_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_user_id_idx" ON "template_stars" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_template_id_idx" ON "template_stars" USING btree ("template_id");--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_user_template_idx" ON "template_stars" USING btree ("user_id","template_id");--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_template_user_idx" ON "template_stars" USING btree ("template_id","user_id");--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_starred_at_idx" ON "template_stars" USING btree ("starred_at");--> statement-breakpoint
|
||||
CREATE INDEX "template_stars_template_starred_at_idx" ON "template_stars" USING btree ("template_id","starred_at");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "template_stars_user_template_unique" ON "template_stars" USING btree ("user_id","template_id");--> statement-breakpoint
|
||||
CREATE INDEX "templates_workflow_id_idx" ON "templates" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "templates_user_id_idx" ON "templates" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "templates_category_idx" ON "templates" USING btree ("category");--> statement-breakpoint
|
||||
CREATE INDEX "templates_views_idx" ON "templates" USING btree ("views");--> statement-breakpoint
|
||||
CREATE INDEX "templates_stars_idx" ON "templates" USING btree ("stars");--> statement-breakpoint
|
||||
CREATE INDEX "templates_category_views_idx" ON "templates" USING btree ("category","views");--> statement-breakpoint
|
||||
CREATE INDEX "templates_category_stars_idx" ON "templates" USING btree ("category","stars");--> statement-breakpoint
|
||||
CREATE INDEX "templates_user_category_idx" ON "templates" USING btree ("user_id","category");--> statement-breakpoint
|
||||
CREATE INDEX "templates_created_at_idx" ON "templates" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "templates_updated_at_idx" ON "templates" USING btree ("updated_at");
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "settings" ADD COLUMN "console_expanded_by_default" boolean DEFAULT true NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "settings" DROP COLUMN "debug_mode";
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE "user_rate_limits" (
|
||||
"user_id" text PRIMARY KEY NOT NULL,
|
||||
"sync_api_requests" integer DEFAULT 0 NOT NULL,
|
||||
"async_api_requests" integer DEFAULT 0 NOT NULL,
|
||||
"window_start" timestamp DEFAULT now() NOT NULL,
|
||||
"last_request_at" timestamp DEFAULT now() NOT NULL,
|
||||
"is_rate_limited" boolean DEFAULT false NOT NULL,
|
||||
"rate_limit_reset_at" timestamp
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user_rate_limits" ADD CONSTRAINT "user_rate_limits_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE "workflow_schedule" DROP CONSTRAINT "workflow_schedule_workflow_id_unique";--> statement-breakpoint
|
||||
ALTER TABLE "webhook" ADD COLUMN "block_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_schedule" ADD COLUMN "block_id" text;--> statement-breakpoint
|
||||
ALTER TABLE "webhook" ADD CONSTRAINT "webhook_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_schedule" ADD CONSTRAINT "workflow_schedule_block_id_workflow_blocks_id_fk" FOREIGN KEY ("block_id") REFERENCES "public"."workflow_blocks"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "workflow_schedule_workflow_block_unique" ON "workflow_schedule" USING btree ("workflow_id","block_id");
|
||||
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE "copilot_checkpoints" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"chat_id" uuid NOT NULL,
|
||||
"yaml" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "copilot_checkpoints" ADD CONSTRAINT "copilot_checkpoints_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_checkpoints" ADD CONSTRAINT "copilot_checkpoints_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_checkpoints" ADD CONSTRAINT "copilot_checkpoints_chat_id_copilot_chats_id_fk" FOREIGN KEY ("chat_id") REFERENCES "public"."copilot_chats"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_user_id_idx" ON "copilot_checkpoints" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_workflow_id_idx" ON "copilot_checkpoints" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_chat_id_idx" ON "copilot_checkpoints" USING btree ("chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_user_workflow_idx" ON "copilot_checkpoints" USING btree ("user_id","workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_workflow_chat_idx" ON "copilot_checkpoints" USING btree ("workflow_id","chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_created_at_idx" ON "copilot_checkpoints" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_checkpoints_chat_created_at_idx" ON "copilot_checkpoints" USING btree ("chat_id","created_at");
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE INDEX "workflow_user_id_idx" ON "workflow" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_workspace_id_idx" ON "workflow" USING btree ("workspace_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_user_workspace_idx" ON "workflow" USING btree ("user_id","workspace_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_logs_workflow_id_idx" ON "workflow_logs" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_logs_workflow_created_idx" ON "workflow_logs" USING btree ("workflow_id","created_at");
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "knowledge_base" DROP CONSTRAINT "knowledge_base_workspace_id_workspace_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "knowledge_base" ADD CONSTRAINT "knowledge_base_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE no action ON UPDATE no action;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "workflow_execution_blocks" CASCADE;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "workflow_logs" CASCADE;
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "knowledge_base_tag_definitions" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"knowledge_base_id" text NOT NULL,
|
||||
"tag_slot" text NOT NULL,
|
||||
"display_name" text NOT NULL,
|
||||
"field_type" text DEFAULT 'text' NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "knowledge_base_tag_definitions" ADD CONSTRAINT "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk" FOREIGN KEY ("knowledge_base_id") REFERENCES "public"."knowledge_base"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "kb_tag_definitions_kb_slot_idx" ON "knowledge_base_tag_definitions" USING btree ("knowledge_base_id","tag_slot");--> statement-breakpoint
|
||||
CREATE INDEX "kb_tag_definitions_kb_id_idx" ON "knowledge_base_tag_definitions" USING btree ("knowledge_base_id");
|
||||
@@ -0,0 +1,3 @@
|
||||
DROP INDEX "workflow_execution_logs_cost_idx";--> statement-breakpoint
|
||||
DROP INDEX "workflow_execution_logs_duration_idx";--> statement-breakpoint
|
||||
CREATE INDEX "workflow_execution_logs_workflow_started_at_idx" ON "workflow_execution_logs" USING btree ("workflow_id","started_at");
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE "workflow_checkpoints" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"workflow_id" text NOT NULL,
|
||||
"chat_id" uuid NOT NULL,
|
||||
"message_id" text,
|
||||
"workflow_state" json NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP TABLE "copilot_checkpoints" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_chats" ADD COLUMN "preview_yaml" text;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_checkpoints" ADD CONSTRAINT "workflow_checkpoints_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_checkpoints" ADD CONSTRAINT "workflow_checkpoints_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "workflow_checkpoints" ADD CONSTRAINT "workflow_checkpoints_chat_id_copilot_chats_id_fk" FOREIGN KEY ("chat_id") REFERENCES "public"."copilot_chats"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_user_id_idx" ON "workflow_checkpoints" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_workflow_id_idx" ON "workflow_checkpoints" USING btree ("workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_chat_id_idx" ON "workflow_checkpoints" USING btree ("chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_message_id_idx" ON "workflow_checkpoints" USING btree ("message_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_user_workflow_idx" ON "workflow_checkpoints" USING btree ("user_id","workflow_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_workflow_chat_idx" ON "workflow_checkpoints" USING btree ("workflow_id","chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_created_at_idx" ON "workflow_checkpoints" USING btree ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "workflow_checkpoints_chat_created_at_idx" ON "workflow_checkpoints" USING btree ("chat_id","created_at");
|
||||
@@ -0,0 +1,21 @@
|
||||
CREATE TABLE "copilot_feedback" (
|
||||
"feedback_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"chat_id" uuid NOT NULL,
|
||||
"user_query" text NOT NULL,
|
||||
"agent_response" text NOT NULL,
|
||||
"is_positive" boolean NOT NULL,
|
||||
"feedback" text,
|
||||
"workflow_yaml" text,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ALTER COLUMN "current_usage_limit" SET DEFAULT '10';--> statement-breakpoint
|
||||
ALTER TABLE "copilot_feedback" ADD CONSTRAINT "copilot_feedback_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_feedback" ADD CONSTRAINT "copilot_feedback_chat_id_copilot_chats_id_fk" FOREIGN KEY ("chat_id") REFERENCES "public"."copilot_chats"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "copilot_feedback_user_id_idx" ON "copilot_feedback" USING btree ("user_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_feedback_chat_id_idx" ON "copilot_feedback" USING btree ("chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_feedback_user_chat_idx" ON "copilot_feedback" USING btree ("user_id","chat_id");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_feedback_is_positive_idx" ON "copilot_feedback" USING btree ("is_positive");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_feedback_created_at_idx" ON "copilot_feedback" USING btree ("created_at");
|
||||
@@ -0,0 +1 @@
|
||||
CREATE UNIQUE INDEX "kb_tag_definitions_kb_display_name_idx" ON "knowledge_base_tag_definitions" USING btree ("knowledge_base_id","display_name");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_execution_logs" ADD COLUMN "files" jsonb;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow_blocks" ADD COLUMN "trigger_mode" boolean DEFAULT false NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "knowledge_base" ALTER COLUMN "chunking_config" SET DEFAULT '{"maxSize": 1024, "minSize": 1, "overlap": 200}';
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" ADD COLUMN "pinned_api_key" text;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "copilot_chats" ADD COLUMN "conversation_id" text;
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "settings" DROP COLUMN "telemetry_notified_user";
|
||||
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "copilot_api_keys" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"api_key_encrypted" text NOT NULL,
|
||||
"api_key_lookup" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_copilot_cost" numeric DEFAULT '0' NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_copilot_tokens" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_copilot_calls" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "copilot_api_keys" ADD CONSTRAINT "copilot_api_keys_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "copilot_api_keys_api_key_encrypted_hash_idx" ON "copilot_api_keys" USING hash ("api_key_encrypted");--> statement-breakpoint
|
||||
CREATE INDEX "copilot_api_keys_lookup_hash_idx" ON "copilot_api_keys" USING hash ("api_key_lookup");
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "workflow" DROP COLUMN "state";
|
||||
@@ -0,0 +1,167 @@
|
||||
-- One-shot data migration to create/populate execution_data & cost, then drop legacy columns
|
||||
-- Safe on reruns and across differing prior schemas
|
||||
-- Note: Depending on runner timeouts, might have to be run manually
|
||||
-- 1) Ensure execution_data exists (prefer rename if only metadata exists)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'workflow_execution_logs' AND column_name = 'metadata'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'workflow_execution_logs' AND column_name = 'execution_data'
|
||||
) THEN
|
||||
EXECUTE 'ALTER TABLE workflow_execution_logs RENAME COLUMN metadata TO execution_data';
|
||||
END IF;
|
||||
END $$;--> statement-breakpoint
|
||||
|
||||
ALTER TABLE "workflow_execution_logs"
|
||||
ADD COLUMN IF NOT EXISTS "execution_data" jsonb NOT NULL DEFAULT '{}'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS "cost" jsonb;--> statement-breakpoint
|
||||
|
||||
-- Process the backfill in batches to avoid large temporary files on big datasets
|
||||
DO $$
|
||||
DECLARE
|
||||
v_batch_size integer := 500; -- keep batches small to avoid timeouts/spills
|
||||
v_rows_updated integer := 0;
|
||||
v_rows_selected integer := 0;
|
||||
v_last_id text := '';
|
||||
v_last_created_at timestamp := '1970-01-01 00:00:00';
|
||||
BEGIN
|
||||
-- modest per-statement timeout; adjust based on observed per-batch runtime
|
||||
PERFORM set_config('statement_timeout', '180s', true);
|
||||
LOOP
|
||||
CREATE TEMP TABLE IF NOT EXISTS _tmp_candidate_ids(id text, created_at timestamp) ON COMMIT DROP;
|
||||
TRUNCATE _tmp_candidate_ids;
|
||||
INSERT INTO _tmp_candidate_ids(id, created_at)
|
||||
SELECT id, created_at
|
||||
FROM workflow_execution_logs
|
||||
WHERE (created_at, id) > (v_last_created_at, v_last_id) AND cost IS NULL
|
||||
ORDER BY created_at, id
|
||||
LIMIT v_batch_size;
|
||||
|
||||
SELECT COUNT(*) INTO v_rows_selected FROM _tmp_candidate_ids;
|
||||
EXIT WHEN v_rows_selected = 0;
|
||||
SELECT created_at, id
|
||||
INTO v_last_created_at, v_last_id
|
||||
FROM _tmp_candidate_ids
|
||||
ORDER BY created_at DESC, id DESC
|
||||
LIMIT 1;
|
||||
|
||||
WITH RECURSIVE
|
||||
spans AS (
|
||||
SELECT l.id, s.span
|
||||
FROM workflow_execution_logs l
|
||||
JOIN _tmp_candidate_ids c ON c.id = l.id
|
||||
LEFT JOIN LATERAL jsonb_array_elements(
|
||||
COALESCE(
|
||||
CASE
|
||||
WHEN jsonb_typeof(l.execution_data->'traceSpans') = 'array' THEN l.execution_data->'traceSpans'
|
||||
ELSE '[]'::jsonb
|
||||
END
|
||||
)
|
||||
) s(span) ON true
|
||||
UNION ALL
|
||||
SELECT spans.id, c.span
|
||||
FROM spans
|
||||
JOIN LATERAL jsonb_array_elements(COALESCE(spans.span->'children','[]'::jsonb)) c(span) ON true
|
||||
),
|
||||
agg AS (
|
||||
SELECT id,
|
||||
SUM(COALESCE((span->'cost'->>'input')::numeric,0)) AS agg_input,
|
||||
SUM(COALESCE((span->'cost'->>'output')::numeric,0)) AS agg_output,
|
||||
SUM(COALESCE((span->'cost'->>'total')::numeric,0)) AS agg_total,
|
||||
SUM(COALESCE((span->'cost'->'tokens'->>'prompt')::numeric, COALESCE((span->'tokens'->>'prompt')::numeric,0))) AS agg_tokens_prompt,
|
||||
SUM(COALESCE((span->'cost'->'tokens'->>'completion')::numeric, COALESCE((span->'tokens'->>'completion')::numeric,0))) AS agg_tokens_completion,
|
||||
SUM(COALESCE((span->'cost'->'tokens'->>'total')::numeric, COALESCE((span->'tokens'->>'total')::numeric,0))) AS agg_tokens_total
|
||||
FROM spans
|
||||
GROUP BY id
|
||||
),
|
||||
model_rows AS (
|
||||
SELECT id,
|
||||
(span->'cost'->>'model') AS model,
|
||||
COALESCE((span->'cost'->>'input')::numeric,0) AS input,
|
||||
COALESCE((span->'cost'->>'output')::numeric,0) AS output,
|
||||
COALESCE((span->'cost'->>'total')::numeric,0) AS total,
|
||||
COALESCE((span->'cost'->'tokens'->>'prompt')::numeric,0) AS tokens_prompt,
|
||||
COALESCE((span->'cost'->'tokens'->>'completion')::numeric,0) AS tokens_completion,
|
||||
COALESCE((span->'cost'->'tokens'->>'total')::numeric,0) AS tokens_total
|
||||
FROM spans
|
||||
WHERE span ? 'cost' AND (span->'cost'->>'model') IS NOT NULL
|
||||
),
|
||||
model_sums AS (
|
||||
SELECT id,
|
||||
model,
|
||||
SUM(input) AS input,
|
||||
SUM(output) AS output,
|
||||
SUM(total) AS total,
|
||||
SUM(tokens_prompt) AS tokens_prompt,
|
||||
SUM(tokens_completion) AS tokens_completion,
|
||||
SUM(tokens_total) AS tokens_total
|
||||
FROM model_rows
|
||||
GROUP BY id, model
|
||||
),
|
||||
models AS (
|
||||
SELECT id,
|
||||
jsonb_object_agg(model, jsonb_build_object(
|
||||
'input', input,
|
||||
'output', output,
|
||||
'total', total,
|
||||
'tokens', jsonb_build_object(
|
||||
'prompt', tokens_prompt,
|
||||
'completion', tokens_completion,
|
||||
'total', tokens_total
|
||||
)
|
||||
)) AS models
|
||||
FROM model_sums
|
||||
GROUP BY id
|
||||
),
|
||||
tb AS (
|
||||
SELECT l.id,
|
||||
NULLIF((l.execution_data->'tokenBreakdown'->>'prompt')::numeric, 0) AS prompt,
|
||||
NULLIF((l.execution_data->'tokenBreakdown'->>'completion')::numeric, 0) AS completion
|
||||
FROM workflow_execution_logs l
|
||||
JOIN _tmp_candidate_ids c ON c.id = l.id
|
||||
)
|
||||
UPDATE workflow_execution_logs AS l
|
||||
SET cost = jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'total', COALESCE((to_jsonb(l)->>'total_cost')::numeric, NULLIF(agg.agg_total,0)),
|
||||
'input', COALESCE((to_jsonb(l)->>'total_input_cost')::numeric, NULLIF(agg.agg_input,0)),
|
||||
'output', COALESCE((to_jsonb(l)->>'total_output_cost')::numeric, NULLIF(agg.agg_output,0)),
|
||||
'tokens', CASE
|
||||
WHEN (to_jsonb(l) ? 'total_tokens') OR tb.prompt IS NOT NULL OR tb.completion IS NOT NULL OR NULLIF(agg.agg_tokens_total,0) IS NOT NULL THEN
|
||||
jsonb_strip_nulls(
|
||||
jsonb_build_object(
|
||||
'total', COALESCE((to_jsonb(l)->>'total_tokens')::numeric, NULLIF(agg.agg_tokens_total,0)),
|
||||
'prompt', COALESCE(tb.prompt, NULLIF(agg.agg_tokens_prompt,0)),
|
||||
'completion', COALESCE(tb.completion, NULLIF(agg.agg_tokens_completion,0))
|
||||
)
|
||||
)
|
||||
ELSE NULL
|
||||
END,
|
||||
'models', models.models
|
||||
)
|
||||
)
|
||||
FROM agg
|
||||
LEFT JOIN models ON models.id = agg.id
|
||||
LEFT JOIN tb ON tb.id = agg.id
|
||||
WHERE l.id = agg.id;
|
||||
|
||||
GET DIAGNOSTICS v_rows_updated = ROW_COUNT;
|
||||
-- continue advancing by id until no more rows are selected
|
||||
END LOOP;
|
||||
END $$;--> statement-breakpoint
|
||||
|
||||
-- 3) Drop legacy columns now that backfill is complete
|
||||
ALTER TABLE "workflow_execution_logs"
|
||||
DROP COLUMN IF EXISTS "message",
|
||||
DROP COLUMN IF EXISTS "block_count",
|
||||
DROP COLUMN IF EXISTS "success_count",
|
||||
DROP COLUMN IF EXISTS "error_count",
|
||||
DROP COLUMN IF EXISTS "skipped_count",
|
||||
DROP COLUMN IF EXISTS "total_cost",
|
||||
DROP COLUMN IF EXISTS "total_input_cost",
|
||||
DROP COLUMN IF EXISTS "total_output_cost",
|
||||
DROP COLUMN IF EXISTS "total_tokens",
|
||||
DROP COLUMN IF EXISTS "metadata";
|
||||
@@ -0,0 +1 @@
|
||||
ALTER TABLE "templates" ALTER COLUMN "workflow_id" DROP NOT NULL;
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE "copilot_api_keys" CASCADE;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user