mirror of
https://github.com/simstudioai/sim.git
synced 2026-04-06 03:00:16 -04:00
Merge staging into lakees/db
- Resolve merge conflicts in input-format.tsx, workflow-block.tsx, providers/utils.ts - Fix tests to use blockData/blockNameMapping for tag variable resolution - Add getBlockOutputs mock to block.test.ts for schema validation tests - Fix normalizeName import path in utils.test.ts - Add sql.raw and sql.join to drizzle-orm mock for sql.test.ts - Add new subBlock types (table-selector, filter-builder, sort-builder) to blocks.test.ts
This commit is contained in:
2
packages/db/migrations/0146_cultured_ikaris.sql
Normal file
2
packages/db/migrations/0146_cultured_ikaris.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_mcp_executions" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "user_stats" ADD COLUMN "total_a2a_executions" integer DEFAULT 0 NOT NULL;
|
||||
10384
packages/db/migrations/meta/0146_snapshot.json
Normal file
10384
packages/db/migrations/meta/0146_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1016,6 +1016,13 @@
|
||||
"when": 1768602646955,
|
||||
"tag": "0145_messy_archangel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 146,
|
||||
"version": "7",
|
||||
"when": 1768867605608,
|
||||
"tag": "0146_cultured_ikaris",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -698,6 +698,8 @@ export const userStats = pgTable('user_stats', {
|
||||
totalWebhookTriggers: integer('total_webhook_triggers').notNull().default(0),
|
||||
totalScheduledExecutions: integer('total_scheduled_executions').notNull().default(0),
|
||||
totalChatExecutions: integer('total_chat_executions').notNull().default(0),
|
||||
totalMcpExecutions: integer('total_mcp_executions').notNull().default(0),
|
||||
totalA2aExecutions: integer('total_a2a_executions').notNull().default(0),
|
||||
totalTokensUsed: integer('total_tokens_used').notNull().default(0),
|
||||
totalCost: decimal('total_cost').notNull().default('0'),
|
||||
currentUsageLimit: decimal('current_usage_limit').default(DEFAULT_FREE_CREDITS.toString()), // Default $20 for free plan, null for team/enterprise
|
||||
|
||||
@@ -18,7 +18,6 @@ import { drizzle } from 'drizzle-orm/postgres-js'
|
||||
import postgres from 'postgres'
|
||||
import { ssoProvider, user } from '../schema'
|
||||
|
||||
// Simple console logger
|
||||
const logger = {
|
||||
info: (message: string, meta?: any) => {
|
||||
const timestamp = new Date().toISOString()
|
||||
@@ -43,7 +42,6 @@ const logger = {
|
||||
},
|
||||
}
|
||||
|
||||
// Get database URL from environment
|
||||
const CONNECTION_STRING = process.env.POSTGRES_URL ?? process.env.DATABASE_URL
|
||||
if (!CONNECTION_STRING) {
|
||||
console.error('❌ POSTGRES_URL or DATABASE_URL environment variable is required')
|
||||
@@ -88,7 +86,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get user
|
||||
const targetUser = await getUser(userEmail)
|
||||
if (!targetUser) {
|
||||
return false
|
||||
@@ -96,7 +93,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
|
||||
|
||||
logger.info(`Found user: ${targetUser.email} (ID: ${targetUser.id})`)
|
||||
|
||||
// Get SSO providers for this user
|
||||
const providers = await db
|
||||
.select()
|
||||
.from(ssoProvider)
|
||||
@@ -112,11 +108,9 @@ async function deregisterSSOProvider(): Promise<boolean> {
|
||||
logger.info(` - Provider ID: ${provider.providerId}, Domain: ${provider.domain}`)
|
||||
}
|
||||
|
||||
// Check if specific provider ID was requested
|
||||
const specificProviderId = process.env.SSO_PROVIDER_ID
|
||||
|
||||
if (specificProviderId) {
|
||||
// Delete specific provider
|
||||
const providerToDelete = providers.find((p) => p.providerId === specificProviderId)
|
||||
if (!providerToDelete) {
|
||||
logger.error(`Provider '${specificProviderId}' not found for user ${targetUser.email}`)
|
||||
@@ -133,7 +127,6 @@ async function deregisterSSOProvider(): Promise<boolean> {
|
||||
`✅ Successfully deleted SSO provider '${specificProviderId}' for user ${targetUser.email}`
|
||||
)
|
||||
} else {
|
||||
// Delete all providers for this user
|
||||
await db.delete(ssoProvider).where(eq(ssoProvider.userId, targetUser.id))
|
||||
|
||||
logger.info(
|
||||
@@ -171,7 +164,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle script execution
|
||||
main().catch((error) => {
|
||||
logger.error('Script execution failed:', { error })
|
||||
process.exit(1)
|
||||
|
||||
@@ -38,7 +38,6 @@ import postgres from 'postgres'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { ssoProvider, user } from '../schema'
|
||||
|
||||
// Self-contained SSO types (matching Better Auth's structure)
|
||||
interface SSOMapping {
|
||||
id: string
|
||||
email: string
|
||||
@@ -108,7 +107,6 @@ interface SSOProviderConfig {
|
||||
samlConfig?: SAMLConfig
|
||||
}
|
||||
|
||||
// Simple console logger (no dependencies)
|
||||
const logger = {
|
||||
info: (message: string, meta?: any) => {
|
||||
const timestamp = new Date().toISOString()
|
||||
@@ -133,14 +131,12 @@ const logger = {
|
||||
},
|
||||
}
|
||||
|
||||
// Get database URL from environment
|
||||
const CONNECTION_STRING = process.env.POSTGRES_URL ?? process.env.DATABASE_URL
|
||||
if (!CONNECTION_STRING) {
|
||||
console.error('❌ POSTGRES_URL or DATABASE_URL environment variable is required')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Initialize database connection (following migration script pattern)
|
||||
const postgresClient = postgres(CONNECTION_STRING, {
|
||||
prepare: false,
|
||||
idle_timeout: 20,
|
||||
@@ -161,7 +157,6 @@ interface SSOProviderData {
|
||||
organizationId?: string
|
||||
}
|
||||
|
||||
// Self-contained configuration builder (no external dependencies)
|
||||
function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
const enabled = process.env.SSO_ENABLED === 'true'
|
||||
if (!enabled) return null
|
||||
@@ -182,7 +177,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
providerType,
|
||||
}
|
||||
|
||||
// Build field mapping
|
||||
config.mapping = {
|
||||
id:
|
||||
process.env.SSO_MAPPING_ID ||
|
||||
@@ -202,7 +196,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
image: process.env.SSO_MAPPING_IMAGE || (providerType === 'oidc' ? 'picture' : undefined),
|
||||
}
|
||||
|
||||
// Build provider-specific configuration
|
||||
if (providerType === 'oidc') {
|
||||
const clientId = process.env.SSO_OIDC_CLIENT_ID
|
||||
const clientSecret = process.env.SSO_OIDC_CLIENT_SECRET
|
||||
@@ -225,7 +218,8 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
userInfoEndpoint: process.env.SSO_OIDC_USERINFO_ENDPOINT,
|
||||
jwksEndpoint: process.env.SSO_OIDC_JWKS_ENDPOINT,
|
||||
discoveryEndpoint:
|
||||
process.env.SSO_OIDC_DISCOVERY_ENDPOINT || `${issuer}/.well-known/openid-configuration`,
|
||||
process.env.SSO_OIDC_DISCOVERY_ENDPOINT ||
|
||||
`${issuer.replace(/\/$/, '')}/.well-known/openid-configuration`,
|
||||
}
|
||||
} else if (providerType === 'saml') {
|
||||
const entryPoint = process.env.SSO_SAML_ENTRY_POINT
|
||||
@@ -237,7 +231,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
|
||||
const callbackUrl = process.env.SSO_SAML_CALLBACK_URL || `${issuer}/callback`
|
||||
|
||||
// Use custom metadata if provided, otherwise generate default
|
||||
let spMetadata = process.env.SSO_SAML_SP_METADATA
|
||||
if (!spMetadata) {
|
||||
spMetadata = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -263,7 +256,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
entityID: issuer,
|
||||
},
|
||||
}
|
||||
// Optionally include IDP metadata if provided
|
||||
const idpMetadata = process.env.SSO_SAML_IDP_METADATA
|
||||
if (idpMetadata) {
|
||||
config.samlConfig.idpMetadata = {
|
||||
@@ -275,7 +267,6 @@ function buildSSOConfigFromEnv(): SSOProviderConfig | null {
|
||||
return config
|
||||
}
|
||||
|
||||
// Self-contained example environment variables function
|
||||
function getExampleEnvVars(
|
||||
providerType: 'oidc' | 'saml',
|
||||
provider?: string
|
||||
@@ -358,7 +349,6 @@ async function getAdminUser(): Promise<{ id: string; email: string } | null> {
|
||||
|
||||
async function registerSSOProvider(): Promise<boolean> {
|
||||
try {
|
||||
// Build configuration from environment variables
|
||||
const ssoConfig = buildSSOConfigFromEnv()
|
||||
|
||||
if (!ssoConfig) {
|
||||
@@ -381,7 +371,6 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
// Get admin user
|
||||
const adminUser = await getAdminUser()
|
||||
if (!adminUser) {
|
||||
return false
|
||||
@@ -394,7 +383,6 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
adminUser: adminUser.email,
|
||||
})
|
||||
|
||||
// Validate issuer URL (same as Better Auth does)
|
||||
try {
|
||||
new URL(ssoConfig.issuer)
|
||||
} catch {
|
||||
@@ -402,7 +390,97 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check if provider already exists
|
||||
if (ssoConfig.providerType === 'oidc' && ssoConfig.oidcConfig) {
|
||||
const needsDiscovery =
|
||||
!ssoConfig.oidcConfig.authorizationEndpoint ||
|
||||
!ssoConfig.oidcConfig.tokenEndpoint ||
|
||||
!ssoConfig.oidcConfig.jwksEndpoint
|
||||
|
||||
if (needsDiscovery) {
|
||||
const discoveryUrl =
|
||||
ssoConfig.oidcConfig.discoveryEndpoint ||
|
||||
`${ssoConfig.issuer.replace(/\/$/, '')}/.well-known/openid-configuration`
|
||||
logger.info('Fetching OIDC discovery document for missing endpoints...', {
|
||||
discoveryUrl,
|
||||
hasAuthEndpoint: !!ssoConfig.oidcConfig.authorizationEndpoint,
|
||||
hasTokenEndpoint: !!ssoConfig.oidcConfig.tokenEndpoint,
|
||||
hasJwksEndpoint: !!ssoConfig.oidcConfig.jwksEndpoint,
|
||||
})
|
||||
|
||||
try {
|
||||
const response = await fetch(discoveryUrl, {
|
||||
headers: { Accept: 'application/json' },
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
logger.error('Failed to fetch OIDC discovery document', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
})
|
||||
logger.error(
|
||||
'Provide all endpoints explicitly via SSO_OIDC_AUTHORIZATION_ENDPOINT, SSO_OIDC_TOKEN_ENDPOINT, SSO_OIDC_JWKS_ENDPOINT'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
const discovery = await response.json()
|
||||
|
||||
ssoConfig.oidcConfig.authorizationEndpoint =
|
||||
ssoConfig.oidcConfig.authorizationEndpoint || discovery.authorization_endpoint
|
||||
ssoConfig.oidcConfig.tokenEndpoint =
|
||||
ssoConfig.oidcConfig.tokenEndpoint || discovery.token_endpoint
|
||||
ssoConfig.oidcConfig.userInfoEndpoint =
|
||||
ssoConfig.oidcConfig.userInfoEndpoint || discovery.userinfo_endpoint
|
||||
ssoConfig.oidcConfig.jwksEndpoint =
|
||||
ssoConfig.oidcConfig.jwksEndpoint || discovery.jwks_uri
|
||||
|
||||
logger.info('Merged OIDC endpoints (user-provided + discovery)', {
|
||||
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
|
||||
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
|
||||
userInfoEndpoint: ssoConfig.oidcConfig.userInfoEndpoint,
|
||||
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Error fetching OIDC discovery document', {
|
||||
error: error instanceof Error ? error.message : 'Unknown error',
|
||||
discoveryUrl,
|
||||
})
|
||||
logger.error(
|
||||
'Please provide explicit endpoints via SSO_OIDC_AUTHORIZATION_ENDPOINT, SSO_OIDC_TOKEN_ENDPOINT, SSO_OIDC_JWKS_ENDPOINT'
|
||||
)
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
logger.info('Using explicitly provided OIDC endpoints (all present)', {
|
||||
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
|
||||
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
|
||||
userInfoEndpoint: ssoConfig.oidcConfig.userInfoEndpoint,
|
||||
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
!ssoConfig.oidcConfig.authorizationEndpoint ||
|
||||
!ssoConfig.oidcConfig.tokenEndpoint ||
|
||||
!ssoConfig.oidcConfig.jwksEndpoint
|
||||
) {
|
||||
const missing: string[] = []
|
||||
if (!ssoConfig.oidcConfig.authorizationEndpoint)
|
||||
missing.push('SSO_OIDC_AUTHORIZATION_ENDPOINT')
|
||||
if (!ssoConfig.oidcConfig.tokenEndpoint) missing.push('SSO_OIDC_TOKEN_ENDPOINT')
|
||||
if (!ssoConfig.oidcConfig.jwksEndpoint) missing.push('SSO_OIDC_JWKS_ENDPOINT')
|
||||
|
||||
logger.error('Missing required OIDC endpoints after discovery merge', {
|
||||
missing,
|
||||
authorizationEndpoint: ssoConfig.oidcConfig.authorizationEndpoint,
|
||||
tokenEndpoint: ssoConfig.oidcConfig.tokenEndpoint,
|
||||
jwksEndpoint: ssoConfig.oidcConfig.jwksEndpoint,
|
||||
})
|
||||
logger.error(`Please provide: ${missing.join(', ')}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const existingProviders = await db
|
||||
.select()
|
||||
.from(ssoProvider)
|
||||
@@ -413,9 +491,8 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
logger.info('Updating existing provider...')
|
||||
}
|
||||
|
||||
// Build provider data (following Better Auth's exact structure)
|
||||
const providerData: SSOProviderData = {
|
||||
id: uuidv4(), // Generate unique ID
|
||||
id: uuidv4(),
|
||||
issuer: ssoConfig.issuer,
|
||||
domain: ssoConfig.domain,
|
||||
userId: adminUser.id,
|
||||
@@ -423,7 +500,6 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
organizationId: process.env.SSO_ORGANIZATION_ID || undefined,
|
||||
}
|
||||
|
||||
// Build OIDC config (same as Better Auth endpoint)
|
||||
if (ssoConfig.providerType === 'oidc' && ssoConfig.oidcConfig) {
|
||||
const oidcConfig = {
|
||||
issuer: ssoConfig.issuer,
|
||||
@@ -445,7 +521,6 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
providerData.oidcConfig = JSON.stringify(oidcConfig)
|
||||
}
|
||||
|
||||
// Build SAML config (same as Better Auth endpoint)
|
||||
if (ssoConfig.providerType === 'saml' && ssoConfig.samlConfig) {
|
||||
const samlConfig = {
|
||||
issuer: ssoConfig.issuer,
|
||||
@@ -467,7 +542,6 @@ async function registerSSOProvider(): Promise<boolean> {
|
||||
providerData.samlConfig = JSON.stringify(samlConfig)
|
||||
}
|
||||
|
||||
// Insert or update the SSO provider record
|
||||
if (existingProviders.length > 0) {
|
||||
await db
|
||||
.update(ssoProvider)
|
||||
@@ -521,7 +595,6 @@ async function main() {
|
||||
console.log('This script directly inserts SSO provider records into the database.')
|
||||
console.log("It follows Better Auth's exact registerSSOProvider logic.\n")
|
||||
|
||||
// Register the SSO provider using direct database access
|
||||
const success = await registerSSOProvider()
|
||||
|
||||
if (success) {
|
||||
@@ -538,7 +611,6 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle script execution
|
||||
main().catch((error) => {
|
||||
logger.error('Script execution failed:', { error })
|
||||
process.exit(1)
|
||||
|
||||
@@ -33,22 +33,20 @@ export interface LoggerConfig {
|
||||
enabled?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Get environment variable value
|
||||
* Works in any JavaScript runtime (Node.js, Bun, etc.)
|
||||
*/
|
||||
const getEnvVar = (key: string): string | undefined => {
|
||||
const getNodeEnv = (): string => {
|
||||
if (typeof process !== 'undefined' && process.env) {
|
||||
return process.env[key]
|
||||
return process.env.NODE_ENV || 'development'
|
||||
}
|
||||
return 'development'
|
||||
}
|
||||
|
||||
const getLogLevel = (): string | undefined => {
|
||||
if (typeof process !== 'undefined' && process.env) {
|
||||
return process.env.LOG_LEVEL
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current environment (development, production, test)
|
||||
*/
|
||||
const getNodeEnv = (): string => getEnvVar('NODE_ENV') || 'development'
|
||||
|
||||
/**
|
||||
* Get the minimum log level from environment variable or use defaults
|
||||
* - Development: DEBUG (show all logs)
|
||||
@@ -56,7 +54,7 @@ const getNodeEnv = (): string => getEnvVar('NODE_ENV') || 'development'
|
||||
* - Test: ERROR (only show errors in tests)
|
||||
*/
|
||||
const getMinLogLevel = (): LogLevel => {
|
||||
const logLevelEnv = getEnvVar('LOG_LEVEL')
|
||||
const logLevelEnv = getLogLevel()
|
||||
if (logLevelEnv && Object.values(LogLevel).includes(logLevelEnv as LogLevel)) {
|
||||
return logLevelEnv as LogLevel
|
||||
}
|
||||
@@ -120,7 +118,6 @@ const formatObject = (obj: unknown, isDev: boolean): string => {
|
||||
stack: isDev ? obj.stack : undefined,
|
||||
name: obj.name,
|
||||
}
|
||||
// Copy any additional enumerable properties from the error
|
||||
for (const key of Object.keys(obj)) {
|
||||
if (!(key in errorObj)) {
|
||||
errorObj[key] = (obj as unknown as Record<string, unknown>)[key]
|
||||
@@ -181,7 +178,6 @@ export class Logger {
|
||||
private shouldLog(level: LogLevel): boolean {
|
||||
if (!this.config.enabled) return false
|
||||
|
||||
// In production, only log on server-side (where window is undefined)
|
||||
if (getNodeEnv() === 'production' && typeof window !== 'undefined') {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,11 +5,29 @@ import { vi } from 'vitest'
|
||||
* Mimics drizzle-orm's sql tagged template.
|
||||
*/
|
||||
export function createMockSql() {
|
||||
return (strings: TemplateStringsArray, ...values: any[]) => ({
|
||||
const sqlFn = (strings: TemplateStringsArray, ...values: any[]) => ({
|
||||
strings,
|
||||
values,
|
||||
toSQL: () => ({ sql: strings.join('?'), params: values }),
|
||||
})
|
||||
|
||||
// Add sql.raw method used by some queries
|
||||
sqlFn.raw = (rawSql: string) => ({
|
||||
rawSql,
|
||||
toSQL: () => ({ sql: rawSql, params: [] }),
|
||||
})
|
||||
|
||||
// Add sql.join method used to combine multiple SQL fragments
|
||||
sqlFn.join = (fragments: any[], separator: any) => ({
|
||||
fragments,
|
||||
separator,
|
||||
toSQL: () => ({
|
||||
sql: fragments.map((f) => f?.toSQL?.()?.sql || String(f)).join(separator?.rawSql || ', '),
|
||||
params: fragments.flatMap((f) => f?.toSQL?.()?.params || []),
|
||||
}),
|
||||
})
|
||||
|
||||
return sqlFn
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user