From 09e0a7e19cbbc68f0ded9c80381f3c9ef6f3633a Mon Sep 17 00:00:00 2001 From: waleedlatif Date: Mon, 28 Jul 2025 10:29:49 -0700 Subject: [PATCH 1/5] add annotations for environment variables --- apps/sim/lib/env.ts | 260 ++++++++++++++++++++++++-------------------- 1 file changed, 141 insertions(+), 119 deletions(-) diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 18581f67d36..3eefac5e035 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -1,3 +1,4 @@ +// biome-ignore format: keep alignment for readability import { createEnv } from '@t3-oss/env-nextjs' import { env as runtimeEnv } from 'next-runtime-env' import { z } from 'zod' @@ -8,136 +9,157 @@ import { z } from 'zod' * - Server-side: Falls back to process.env when runtimeEnv returns undefined * - Provides seamless Docker runtime variable support for NEXT_PUBLIC_ vars */ -const getEnv = (variable: string): string | undefined => { - return runtimeEnv(variable) ?? process.env[variable] -} +const getEnv = (variable: string) => runtimeEnv(variable) ?? process.env[variable] export const env = createEnv({ skipValidation: true, server: { - DATABASE_URL: z.string().url(), - BETTER_AUTH_URL: z.string().url(), - BETTER_AUTH_SECRET: z.string().min(32), - DISABLE_REGISTRATION: z.boolean().optional(), - ENCRYPTION_KEY: z.string().min(32), - INTERNAL_API_SECRET: z.string().min(32), - - POSTGRES_URL: z.string().url().optional(), - STRIPE_SECRET_KEY: z.string().min(1).optional(), - STRIPE_BILLING_WEBHOOK_SECRET: z.string().min(1).optional(), - STRIPE_WEBHOOK_SECRET: z.string().min(1).optional(), - STRIPE_FREE_PRICE_ID: z.string().min(1).optional(), - FREE_TIER_COST_LIMIT: z.number().optional(), - STRIPE_PRO_PRICE_ID: z.string().min(1).optional(), - PRO_TIER_COST_LIMIT: z.number().optional(), - STRIPE_TEAM_PRICE_ID: z.string().min(1).optional(), - TEAM_TIER_COST_LIMIT: z.number().optional(), - STRIPE_ENTERPRISE_PRICE_ID: z.string().min(1).optional(), - ENTERPRISE_TIER_COST_LIMIT: z.number().optional(), - RESEND_API_KEY: z.string().min(1).optional(), - EMAIL_DOMAIN: z.string().min(1).optional(), - OPENAI_API_KEY: z.string().min(1).optional(), - OPENAI_API_KEY_1: z.string().min(1).optional(), - OPENAI_API_KEY_2: z.string().min(1).optional(), - OPENAI_API_KEY_3: z.string().min(1).optional(), - MISTRAL_API_KEY: z.string().min(1).optional(), - ANTHROPIC_API_KEY_1: z.string().min(1).optional(), - ANTHROPIC_API_KEY_2: z.string().min(1).optional(), - ANTHROPIC_API_KEY_3: z.string().min(1).optional(), - FREESTYLE_API_KEY: z.string().min(1).optional(), - TELEMETRY_ENDPOINT: z.string().url().optional(), - COST_MULTIPLIER: z.number().optional(), - JWT_SECRET: z.string().min(1).optional(), - BROWSERBASE_API_KEY: z.string().min(1).optional(), - BROWSERBASE_PROJECT_ID: z.string().min(1).optional(), - OLLAMA_URL: z.string().url().optional(), - SENTRY_ORG: z.string().optional(), - SENTRY_PROJECT: z.string().optional(), - SENTRY_AUTH_TOKEN: z.string().optional(), - REDIS_URL: z.string().url().optional(), - NEXT_RUNTIME: z.string().optional(), - VERCEL_ENV: z.string().optional(), - - // Trigger.dev - TRIGGER_SECRET_KEY: z.string().min(1).optional(), - - // Storage - AWS_REGION: z.string().optional(), - AWS_ACCESS_KEY_ID: z.string().optional(), - AWS_SECRET_ACCESS_KEY: z.string().optional(), - S3_BUCKET_NAME: z.string().optional(), - S3_LOGS_BUCKET_NAME: z.string().optional(), - S3_KB_BUCKET_NAME: z.string().optional(), - AZURE_ACCOUNT_NAME: z.string().optional(), - AZURE_ACCOUNT_KEY: z.string().optional(), - AZURE_CONNECTION_STRING: z.string().optional(), - AZURE_STORAGE_CONTAINER_NAME: z.string().optional(), - AZURE_STORAGE_KB_CONTAINER_NAME: z.string().optional(), - - // Miscellaneous - CRON_SECRET: z.string().optional(), - FREE_PLAN_LOG_RETENTION_DAYS: z.string().optional(), - GITHUB_TOKEN: z.string().optional(), - ELEVENLABS_API_KEY: z.string().min(1).optional(), - AZURE_OPENAI_ENDPOINT: z.string().url().optional(), - AZURE_OPENAI_API_VERSION: z.string().optional(), - - // OAuth blocks (all optional) - GOOGLE_CLIENT_ID: z.string().optional(), - GOOGLE_CLIENT_SECRET: z.string().optional(), - GITHUB_CLIENT_ID: z.string().optional(), - GITHUB_CLIENT_SECRET: z.string().optional(), - GITHUB_REPO_CLIENT_ID: z.string().optional(), - GITHUB_REPO_CLIENT_SECRET: z.string().optional(), - X_CLIENT_ID: z.string().optional(), - X_CLIENT_SECRET: z.string().optional(), - CONFLUENCE_CLIENT_ID: z.string().optional(), - CONFLUENCE_CLIENT_SECRET: z.string().optional(), - JIRA_CLIENT_ID: z.string().optional(), - JIRA_CLIENT_SECRET: z.string().optional(), - AIRTABLE_CLIENT_ID: z.string().optional(), - AIRTABLE_CLIENT_SECRET: z.string().optional(), - SUPABASE_CLIENT_ID: z.string().optional(), - SUPABASE_CLIENT_SECRET: z.string().optional(), - NOTION_CLIENT_ID: z.string().optional(), - NOTION_CLIENT_SECRET: z.string().optional(), - DISCORD_CLIENT_ID: z.string().optional(), - DISCORD_CLIENT_SECRET: z.string().optional(), - MICROSOFT_CLIENT_ID: z.string().optional(), - MICROSOFT_CLIENT_SECRET: z.string().optional(), - HUBSPOT_CLIENT_ID: z.string().optional(), - HUBSPOT_CLIENT_SECRET: z.string().optional(), - WEALTHBOX_CLIENT_ID: z.string().optional(), - WEALTHBOX_CLIENT_SECRET: z.string().optional(), - DOCKER_BUILD: z.boolean().optional(), - LINEAR_CLIENT_ID: z.string().optional(), - LINEAR_CLIENT_SECRET: z.string().optional(), - SLACK_CLIENT_ID: z.string().optional(), - SLACK_CLIENT_SECRET: z.string().optional(), - REDDIT_CLIENT_ID: z.string().optional(), - REDDIT_CLIENT_SECRET: z.string().optional(), - SOCKET_SERVER_URL: z.string().url().optional(), - SOCKET_PORT: z.number().optional(), - PORT: z.number().optional(), - ALLOWED_ORIGINS: z.string().optional(), - JOB_RETENTION_DAYS: z.string().optional().default('1'), + // Core Database & Authentication + DATABASE_URL: z.string().url(), // Primary database connection string + BETTER_AUTH_URL: z.string().url(), // Base URL for Better Auth service + BETTER_AUTH_SECRET: z.string().min(32), // Secret key for Better Auth JWT signing + DISABLE_REGISTRATION: z.boolean().optional(), // Flag to disable new user registration + ENCRYPTION_KEY: z.string().min(32), // Key for encrypting sensitive data + INTERNAL_API_SECRET: z.string().min(32), // Secret for internal API authentication + + // Database & Storage + POSTGRES_URL: z.string().url().optional(), // Alternative PostgreSQL connection string + REDIS_URL: z.string().url().optional(), // Redis connection string for caching/sessions + + // Payment & Billing (Stripe) + STRIPE_SECRET_KEY: z.string().min(1).optional(), // Stripe secret key for payment processing + STRIPE_BILLING_WEBHOOK_SECRET: z.string().min(1).optional(), // Webhook secret for billing events + STRIPE_WEBHOOK_SECRET: z.string().min(1).optional(), // General Stripe webhook secret + STRIPE_FREE_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for free tier + FREE_TIER_COST_LIMIT: z.number().optional(), // Cost limit for free tier users + STRIPE_PRO_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for pro tier + PRO_TIER_COST_LIMIT: z.number().optional(), // Cost limit for pro tier users + STRIPE_TEAM_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for team tier + TEAM_TIER_COST_LIMIT: z.number().optional(), // Cost limit for team tier users + STRIPE_ENTERPRISE_PRICE_ID: z.string().min(1).optional(), // Stripe price ID for enterprise tier + ENTERPRISE_TIER_COST_LIMIT: z.number().optional(), // Cost limit for enterprise tier users + + // Email & Communication + RESEND_API_KEY: z.string().min(1).optional(), // Resend API key for transactional emails + EMAIL_DOMAIN: z.string().min(1).optional(), // Domain for sending emails + + // AI/LLM Provider API Keys + OPENAI_API_KEY: z.string().min(1).optional(), // Primary OpenAI API key + OPENAI_API_KEY_1: z.string().min(1).optional(), // Additional OpenAI API key for load balancing + OPENAI_API_KEY_2: z.string().min(1).optional(), // Additional OpenAI API key for load balancing + OPENAI_API_KEY_3: z.string().min(1).optional(), // Additional OpenAI API key for load balancing + MISTRAL_API_KEY: z.string().min(1).optional(), // Mistral AI API key + ANTHROPIC_API_KEY_1: z.string().min(1).optional(), // Primary Anthropic Claude API key + ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing + ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing + FREESTYLE_API_KEY: z.string().min(1).optional(), // Freestyle AI API key + AZURE_OPENAI_ENDPOINT: z.string().url().optional(), // Azure OpenAI service endpoint + AZURE_OPENAI_API_VERSION: z.string().optional(), // Azure OpenAI API version + OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL + ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for text-to-speech in deployed chat + + // Monitoring & Analytics + TELEMETRY_ENDPOINT: z.string().url().optional(), // Custom telemetry/analytics endpoint + COST_MULTIPLIER: z.number().optional(), // Multiplier for cost calculations + SENTRY_ORG: z.string().optional(), // Sentry organization for error tracking + SENTRY_PROJECT: z.string().optional(), // Sentry project for error tracking + SENTRY_AUTH_TOKEN: z.string().optional(), // Sentry authentication token + + // External Services + JWT_SECRET: z.string().min(1).optional(), // JWT signing secret for custom tokens + BROWSERBASE_API_KEY: z.string().min(1).optional(), // Browserbase API key for browser automation + BROWSERBASE_PROJECT_ID: z.string().min(1).optional(), // Browserbase project ID + GITHUB_TOKEN: z.string().optional(), // GitHub personal access token for API access + + // Infrastructure & Deployment + NEXT_RUNTIME: z.string().optional(), // Next.js runtime environment + VERCEL_ENV: z.string().optional(), // Vercel deployment environment + DOCKER_BUILD: z.boolean().optional(), // Flag indicating Docker build environment + + // Background Jobs & Scheduling + TRIGGER_SECRET_KEY: z.string().min(1).optional(), // Trigger.dev secret key for background jobs + CRON_SECRET: z.string().optional(), // Secret for authenticating cron job requests + JOB_RETENTION_DAYS: z.string().optional().default('1'), // Days to retain job logs/data + + // Cloud Storage - AWS S3 + AWS_REGION: z.string().optional(), // AWS region for S3 buckets + AWS_ACCESS_KEY_ID: z.string().optional(), // AWS access key ID + AWS_SECRET_ACCESS_KEY: z.string().optional(), // AWS secret access key + S3_BUCKET_NAME: z.string().optional(), // S3 bucket for general file storage + S3_LOGS_BUCKET_NAME: z.string().optional(), // S3 bucket for storing logs + S3_KB_BUCKET_NAME: z.string().optional(), // S3 bucket for knowledge base files + + // Cloud Storage - Azure + AZURE_ACCOUNT_NAME: z.string().optional(), // Azure storage account name + AZURE_ACCOUNT_KEY: z.string().optional(), // Azure storage account key + AZURE_CONNECTION_STRING: z.string().optional(), // Azure storage connection string + AZURE_STORAGE_CONTAINER_NAME: z.string().optional(), // Azure container for general files + AZURE_STORAGE_KB_CONTAINER_NAME: z.string().optional(), // Azure container for knowledge base files + + // Data Retention + FREE_PLAN_LOG_RETENTION_DAYS: z.string().optional(), // Log retention days for free plan users + + // Real-time Communication + SOCKET_SERVER_URL: z.string().url().optional(), // WebSocket server URL for real-time features + SOCKET_PORT: z.number().optional(), // Port for WebSocket server + PORT: z.number().optional(), // Main application port + ALLOWED_ORIGINS: z.string().optional(), // CORS allowed origins + + // OAuth Integration Credentials - All optional, enables third-party integrations + GOOGLE_CLIENT_ID: z.string().optional(), // Google OAuth client ID for Google services + GOOGLE_CLIENT_SECRET: z.string().optional(), // Google OAuth client secret + GITHUB_CLIENT_ID: z.string().optional(), // GitHub OAuth client ID for GitHub integration + GITHUB_CLIENT_SECRET: z.string().optional(), // GitHub OAuth client secret + GITHUB_REPO_CLIENT_ID: z.string().optional(), // GitHub OAuth client ID for repo access + GITHUB_REPO_CLIENT_SECRET: z.string().optional(), // GitHub OAuth client secret for repo access + X_CLIENT_ID: z.string().optional(), // X (Twitter) OAuth client ID + X_CLIENT_SECRET: z.string().optional(), // X (Twitter) OAuth client secret + CONFLUENCE_CLIENT_ID: z.string().optional(), // Atlassian Confluence OAuth client ID + CONFLUENCE_CLIENT_SECRET: z.string().optional(), // Atlassian Confluence OAuth client secret + JIRA_CLIENT_ID: z.string().optional(), // Atlassian Jira OAuth client ID + JIRA_CLIENT_SECRET: z.string().optional(), // Atlassian Jira OAuth client secret + AIRTABLE_CLIENT_ID: z.string().optional(), // Airtable OAuth client ID + AIRTABLE_CLIENT_SECRET: z.string().optional(), // Airtable OAuth client secret + SUPABASE_CLIENT_ID: z.string().optional(), // Supabase OAuth client ID + SUPABASE_CLIENT_SECRET: z.string().optional(), // Supabase OAuth client secret + NOTION_CLIENT_ID: z.string().optional(), // Notion OAuth client ID + NOTION_CLIENT_SECRET: z.string().optional(), // Notion OAuth client secret + DISCORD_CLIENT_ID: z.string().optional(), // Discord OAuth client ID + DISCORD_CLIENT_SECRET: z.string().optional(), // Discord OAuth client secret + MICROSOFT_CLIENT_ID: z.string().optional(), // Microsoft OAuth client ID for Office 365/Teams + MICROSOFT_CLIENT_SECRET: z.string().optional(), // Microsoft OAuth client secret + HUBSPOT_CLIENT_ID: z.string().optional(), // HubSpot OAuth client ID + HUBSPOT_CLIENT_SECRET: z.string().optional(), // HubSpot OAuth client secret + WEALTHBOX_CLIENT_ID: z.string().optional(), // WealthBox OAuth client ID + WEALTHBOX_CLIENT_SECRET: z.string().optional(), // WealthBox OAuth client secret + LINEAR_CLIENT_ID: z.string().optional(), // Linear OAuth client ID + LINEAR_CLIENT_SECRET: z.string().optional(), // Linear OAuth client secret + SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID + SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret + REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID + REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret }, client: { - NEXT_PUBLIC_APP_URL: z.string().url(), - NEXT_PUBLIC_VERCEL_URL: z.string().optional(), - NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(), - NEXT_PUBLIC_GOOGLE_CLIENT_ID: z.string().optional(), - NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), - NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: z.string().optional(), - NEXT_PUBLIC_SOCKET_URL: z.string().url().optional(), + // Core Application URLs - Required for frontend functionality + NEXT_PUBLIC_APP_URL: z.string().url(), // Base URL of the application (e.g., https://app.simstudio.ai) + NEXT_PUBLIC_VERCEL_URL: z.string().optional(), // Vercel deployment URL for preview/production + + // Client-side Services + NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(), // Sentry DSN for client-side error tracking + NEXT_PUBLIC_SOCKET_URL: z.string().url().optional(), // WebSocket server URL for real-time features + + // Google Services - For client-side Google integrations + NEXT_PUBLIC_GOOGLE_CLIENT_ID: z.string().optional(), // Google OAuth client ID for browser auth + NEXT_PUBLIC_GOOGLE_API_KEY: z.string().optional(), // Google API key for client-side API calls + NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: z.string().optional(), // Google project number for Drive picker }, // Variables available on both server and client shared: { - NODE_ENV: z.enum(['development', 'test', 'production']).optional(), - NEXT_TELEMETRY_DISABLED: z.string().optional(), + NODE_ENV: z.enum(['development', 'test', 'production']).optional(), // Runtime environment + NEXT_TELEMETRY_DISABLED: z.string().optional(), // Disable Next.js telemetry collection }, experimental__runtimeEnv: { @@ -153,7 +175,7 @@ export const env = createEnv({ }, }) -// Needing this utility because t3-env is returning string for boolean values. +// Need this utility because t3-env is returning string for boolean values. export const isTruthy = (value: string | boolean | number | undefined) => typeof value === 'string' ? value === 'true' || value === '1' : Boolean(value) From 96bf54e1b5ff0f11669ac20997734f5c6b9331a3 Mon Sep 17 00:00:00 2001 From: waleedlatif Date: Mon, 28 Jul 2025 10:44:10 -0700 Subject: [PATCH 2/5] selectively enable vercel speed insights --- apps/sim/app/layout.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index e46684e6ca5..09b607a7657 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -4,12 +4,25 @@ import type { Metadata, Viewport } from 'next' import { PublicEnvScript } from 'next-runtime-env' import { createLogger } from '@/lib/logs/console/logger' import { TelemetryConsentDialog } from '@/app/telemetry-consent-dialog' +import { env, isTruthy } from '@/lib/env' import '@/app/globals.css' import { ZoomPrevention } from '@/app/zoom-prevention' const logger = createLogger('RootLayout') +const shouldEnableAnalytics = () => { + if (isTruthy(env.NEXT_TELEMETRY_DISABLED)) { + return false + } + + if (!env.VERCEL_ENV) { + return false + } + + return true +} + const BROWSER_EXTENSION_ATTRIBUTES = [ 'data-new-gr-c-s-check-loaded', 'data-gr-ext-installed', @@ -226,8 +239,12 @@ export default function RootLayout({ children }: { children: React.ReactNode }) {children} - - + {shouldEnableAnalytics() && ( + <> + + + + )} ) From 9b40d59c52fdae12bce212fd4e4ee1373cf45c09 Mon Sep 17 00:00:00 2001 From: waleedlatif Date: Mon, 28 Jul 2025 10:55:52 -0700 Subject: [PATCH 3/5] use DOCKER_BUILD flag to selectively disable vercel speed insights and analytics --- apps/sim/app/layout.tsx | 2 +- apps/sim/stores/organization/store.ts | 64 ++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index 09b607a7657..067ac7125a0 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -12,7 +12,7 @@ import { ZoomPrevention } from '@/app/zoom-prevention' const logger = createLogger('RootLayout') const shouldEnableAnalytics = () => { - if (isTruthy(env.NEXT_TELEMETRY_DISABLED)) { + if (isTruthy(env.DOCKER_BUILD)) { return false } diff --git a/apps/sim/stores/organization/store.ts b/apps/sim/stores/organization/store.ts index 7c39880894c..899f9031b2d 100644 --- a/apps/sim/stores/organization/store.ts +++ b/apps/sim/stores/organization/store.ts @@ -64,24 +64,52 @@ export const useOrganizationStore = create()( set({ isLoading: true, error: null }) try { - // Load organizations, active organization, and user subscription info in parallel - const [orgsResponse, activeOrgResponse, billingResponse] = await Promise.all([ - client.organization.list(), - client.organization.getFullOrganization().catch(() => ({ data: null })), - fetch('/api/billing?context=user'), - ]) - - const organizations = orgsResponse.data || [] - const activeOrganization = activeOrgResponse.data || null - + // First check billing data to see if user has team/enterprise plans + const billingResponse = await fetch('/api/billing?context=user') + let hasTeamPlan = false let hasEnterprisePlan = false + let shouldLoadOrganizations = false if (billingResponse.ok) { const billingResult = await billingResponse.json() const billingData = billingResult.data hasTeamPlan = billingData.isTeam hasEnterprisePlan = billingData.isEnterprise + shouldLoadOrganizations = hasTeamPlan || hasEnterprisePlan + } + + let organizations: any[] = [] + let activeOrganization: any = null + + // Only load organization data if user has team/enterprise plans + if (shouldLoadOrganizations) { + logger.debug('User has team/enterprise plan, loading organization data') + const [orgsResponse, activeOrgResponse] = await Promise.all([ + client.organization.list().catch((error) => { + // Suppress 404 errors for users who don't have organizations yet + if (error.status === 404 || error.code === 404) { + logger.debug('User has no organizations yet, returning empty list') + return { data: [] } + } + logger.error('Unexpected error loading organizations:', error) + throw error + }), + client.organization.getFullOrganization().catch((error) => { + // Suppress 404 errors for users who don't have an active organization yet + if (error.status === 404 || error.code === 404) { + logger.debug('User has no active organization yet, returning null') + return { data: null } + } + logger.error('Unexpected error loading active organization:', error) + return { data: null } + }), + ]) + + organizations = orgsResponse.data || [] + activeOrganization = activeOrgResponse.data || null + } else { + logger.debug('User has no team/enterprise plan, skipping organization loading') } set({ @@ -328,7 +356,13 @@ export const useOrganizationStore = create()( if (!activeOrganization?.id) return try { - const fullOrgResponse = await client.organization.getFullOrganization() + const fullOrgResponse = await client.organization.getFullOrganization().catch((error) => { + if (error.status === 404) { + logger.debug('User has no active organization (404) during refresh, returning null') + return { data: null } + } + throw error + }) const updatedOrg = fullOrgResponse.data set({ activeOrganization: updatedOrg }) @@ -388,7 +422,13 @@ export const useOrganizationStore = create()( try { await client.organization.setActive({ organizationId: orgId }) - const activeOrgResponse = await client.organization.getFullOrganization() + const activeOrgResponse = await client.organization.getFullOrganization().catch((error) => { + if (error.status === 404) { + logger.debug('User has no active organization (404) during setActive, returning null') + return { data: null } + } + throw error + }) const activeOrganization = activeOrgResponse.data set({ activeOrganization }) From 02769dd9aa2139334f922fb285f9db90dd528bde Mon Sep 17 00:00:00 2001 From: waleedlatif Date: Mon, 28 Jul 2025 11:01:15 -0700 Subject: [PATCH 4/5] lint --- apps/sim/app/layout.tsx | 6 +-- apps/sim/lib/env.ts | 2 +- apps/sim/stores/organization/store.ts | 64 +++++---------------------- 3 files changed, 16 insertions(+), 56 deletions(-) diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index 067ac7125a0..b7a4d0b8bfb 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -2,9 +2,9 @@ import { Analytics } from '@vercel/analytics/next' import { SpeedInsights } from '@vercel/speed-insights/next' import type { Metadata, Viewport } from 'next' import { PublicEnvScript } from 'next-runtime-env' +import { env, isTruthy } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { TelemetryConsentDialog } from '@/app/telemetry-consent-dialog' -import { env, isTruthy } from '@/lib/env' import '@/app/globals.css' import { ZoomPrevention } from '@/app/zoom-prevention' @@ -15,11 +15,11 @@ const shouldEnableAnalytics = () => { if (isTruthy(env.DOCKER_BUILD)) { return false } - + if (!env.VERCEL_ENV) { return false } - + return true } diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 3eefac5e035..4302ce45061 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -1,4 +1,3 @@ -// biome-ignore format: keep alignment for readability import { createEnv } from '@t3-oss/env-nextjs' import { env as runtimeEnv } from 'next-runtime-env' import { z } from 'zod' @@ -11,6 +10,7 @@ import { z } from 'zod' */ const getEnv = (variable: string) => runtimeEnv(variable) ?? process.env[variable] +// biome-ignore format: keep alignment for readability export const env = createEnv({ skipValidation: true, diff --git a/apps/sim/stores/organization/store.ts b/apps/sim/stores/organization/store.ts index 899f9031b2d..7c39880894c 100644 --- a/apps/sim/stores/organization/store.ts +++ b/apps/sim/stores/organization/store.ts @@ -64,52 +64,24 @@ export const useOrganizationStore = create()( set({ isLoading: true, error: null }) try { - // First check billing data to see if user has team/enterprise plans - const billingResponse = await fetch('/api/billing?context=user') - + // Load organizations, active organization, and user subscription info in parallel + const [orgsResponse, activeOrgResponse, billingResponse] = await Promise.all([ + client.organization.list(), + client.organization.getFullOrganization().catch(() => ({ data: null })), + fetch('/api/billing?context=user'), + ]) + + const organizations = orgsResponse.data || [] + const activeOrganization = activeOrgResponse.data || null + let hasTeamPlan = false let hasEnterprisePlan = false - let shouldLoadOrganizations = false if (billingResponse.ok) { const billingResult = await billingResponse.json() const billingData = billingResult.data hasTeamPlan = billingData.isTeam hasEnterprisePlan = billingData.isEnterprise - shouldLoadOrganizations = hasTeamPlan || hasEnterprisePlan - } - - let organizations: any[] = [] - let activeOrganization: any = null - - // Only load organization data if user has team/enterprise plans - if (shouldLoadOrganizations) { - logger.debug('User has team/enterprise plan, loading organization data') - const [orgsResponse, activeOrgResponse] = await Promise.all([ - client.organization.list().catch((error) => { - // Suppress 404 errors for users who don't have organizations yet - if (error.status === 404 || error.code === 404) { - logger.debug('User has no organizations yet, returning empty list') - return { data: [] } - } - logger.error('Unexpected error loading organizations:', error) - throw error - }), - client.organization.getFullOrganization().catch((error) => { - // Suppress 404 errors for users who don't have an active organization yet - if (error.status === 404 || error.code === 404) { - logger.debug('User has no active organization yet, returning null') - return { data: null } - } - logger.error('Unexpected error loading active organization:', error) - return { data: null } - }), - ]) - - organizations = orgsResponse.data || [] - activeOrganization = activeOrgResponse.data || null - } else { - logger.debug('User has no team/enterprise plan, skipping organization loading') } set({ @@ -356,13 +328,7 @@ export const useOrganizationStore = create()( if (!activeOrganization?.id) return try { - const fullOrgResponse = await client.organization.getFullOrganization().catch((error) => { - if (error.status === 404) { - logger.debug('User has no active organization (404) during refresh, returning null') - return { data: null } - } - throw error - }) + const fullOrgResponse = await client.organization.getFullOrganization() const updatedOrg = fullOrgResponse.data set({ activeOrganization: updatedOrg }) @@ -422,13 +388,7 @@ export const useOrganizationStore = create()( try { await client.organization.setActive({ organizationId: orgId }) - const activeOrgResponse = await client.organization.getFullOrganization().catch((error) => { - if (error.status === 404) { - logger.debug('User has no active organization (404) during setActive, returning null') - return { data: null } - } - throw error - }) + const activeOrgResponse = await client.organization.getFullOrganization() const activeOrganization = activeOrgResponse.data set({ activeOrganization }) From dba9ba7905513734da0680ec80f15f1d4f67b51f Mon Sep 17 00:00:00 2001 From: waleedlatif Date: Mon, 28 Jul 2025 11:06:23 -0700 Subject: [PATCH 5/5] additional info --- apps/sim/lib/env.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/sim/lib/env.ts b/apps/sim/lib/env.ts index 4302ce45061..73b562a75d0 100644 --- a/apps/sim/lib/env.ts +++ b/apps/sim/lib/env.ts @@ -54,11 +54,13 @@ export const env = createEnv({ ANTHROPIC_API_KEY_2: z.string().min(1).optional(), // Additional Anthropic API key for load balancing ANTHROPIC_API_KEY_3: z.string().min(1).optional(), // Additional Anthropic API key for load balancing FREESTYLE_API_KEY: z.string().min(1).optional(), // Freestyle AI API key - AZURE_OPENAI_ENDPOINT: z.string().url().optional(), // Azure OpenAI service endpoint - AZURE_OPENAI_API_VERSION: z.string().optional(), // Azure OpenAI API version OLLAMA_URL: z.string().url().optional(), // Ollama local LLM server URL ELEVENLABS_API_KEY: z.string().min(1).optional(), // ElevenLabs API key for text-to-speech in deployed chat + // Azure OpenAI Configuration + AZURE_OPENAI_ENDPOINT: z.string().url().optional(), // Azure OpenAI service endpoint + AZURE_OPENAI_API_VERSION: z.string().optional(), // Azure OpenAI API version + // Monitoring & Analytics TELEMETRY_ENDPOINT: z.string().url().optional(), // Custom telemetry/analytics endpoint COST_MULTIPLIER: z.number().optional(), // Multiplier for cost calculations @@ -90,7 +92,7 @@ export const env = createEnv({ S3_LOGS_BUCKET_NAME: z.string().optional(), // S3 bucket for storing logs S3_KB_BUCKET_NAME: z.string().optional(), // S3 bucket for knowledge base files - // Cloud Storage - Azure + // Cloud Storage - Azure Blob AZURE_ACCOUNT_NAME: z.string().optional(), // Azure storage account name AZURE_ACCOUNT_KEY: z.string().optional(), // Azure storage account key AZURE_CONNECTION_STRING: z.string().optional(), // Azure storage connection string