mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-07 22:24:06 -05:00
improvement(consts): removed redundant default consts in favor of envvar defaults for storage & usage limits (#1737)
* improvement(consts): removed redundant default consts in favor of envvar defaults for storage & usage limits * remove unnecessary tests
This commit is contained in:
@@ -2,9 +2,7 @@ import { Building2 } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
|
||||
import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
|
||||
import { env } from '@/lib/env'
|
||||
import { checkEnterprisePlan, getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
|
||||
|
||||
type Subscription = {
|
||||
id: string
|
||||
@@ -102,7 +100,7 @@ export function TeamSeatsOverview({
|
||||
<span className='font-medium text-sm'>Seats</span>
|
||||
{!checkEnterprisePlan(subscriptionData) ? (
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
(${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT}/month each)
|
||||
(${getTeamTierLimitPerSeat()}/month each)
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
|
||||
import { env } from '@/lib/env'
|
||||
import { getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
|
||||
|
||||
interface TeamSeatsProps {
|
||||
open: boolean
|
||||
@@ -55,7 +54,7 @@ export function TeamSeats({
|
||||
}
|
||||
}, [open, initialSeats])
|
||||
|
||||
const costPerSeat = env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT
|
||||
const costPerSeat = getTeamTierLimitPerSeat()
|
||||
const totalMonthlyCost = selectedSeats * costPerSeat
|
||||
const costChange = currentSeats ? (selectedSeats - currentSeats) * costPerSeat : 0
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Alert, AlertDescription, AlertTitle, Skeleton } from '@/components/ui'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants'
|
||||
import { checkEnterprisePlan } from '@/lib/billing/subscriptions/utils'
|
||||
import { env } from '@/lib/env'
|
||||
import { checkEnterprisePlan, getTeamTierLimitPerSeat } from '@/lib/billing/subscriptions/utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import {
|
||||
MemberInvitationCard,
|
||||
@@ -295,8 +293,7 @@ export function TeamManagement() {
|
||||
<ul className='ml-4 list-disc space-y-2 text-muted-foreground text-xs'>
|
||||
<li>
|
||||
Your team is billed a minimum of $
|
||||
{(subscriptionData?.seats || 0) *
|
||||
(env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT)}
|
||||
{(subscriptionData?.seats || 0) * getTeamTierLimitPerSeat()}
|
||||
/month for {subscriptionData?.seats || 0} licensed seats
|
||||
</li>
|
||||
<li>All team member usage is pooled together from a shared limit</li>
|
||||
@@ -414,7 +411,7 @@ export function TeamManagement() {
|
||||
open={isAddSeatDialogOpen}
|
||||
onOpenChange={setIsAddSeatDialogOpen}
|
||||
title='Add Team Seats'
|
||||
description={`Each seat costs $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT}/month and provides $${env.TEAM_TIER_COST_LIMIT ?? DEFAULT_TEAM_TIER_COST_LIMIT} in monthly inference credits. Adjust the number of licensed seats for your team.`}
|
||||
description={`Each seat costs $${getTeamTierLimitPerSeat()}/month and provides $${getTeamTierLimitPerSeat()} in monthly inference credits. Adjust the number of licensed seats for your team.`}
|
||||
currentSeats={subscriptionData?.seats || 1}
|
||||
initialSeats={newSeatCount}
|
||||
isLoading={isUpdatingSeats}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
|
||||
import { getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('useSubscriptionState')
|
||||
@@ -82,7 +82,7 @@ export function useSubscriptionState() {
|
||||
|
||||
usage: {
|
||||
current: data?.usage?.current ?? 0,
|
||||
limit: data?.usage?.limit ?? DEFAULT_FREE_CREDITS,
|
||||
limit: data?.usage?.limit ?? getFreeTierLimit(),
|
||||
percentUsed: data?.usage?.percentUsed ?? 0,
|
||||
isWarning: data?.usage?.isWarning ?? false,
|
||||
isExceeded: data?.usage?.isExceeded ?? false,
|
||||
@@ -203,9 +203,9 @@ export function useUsageLimit() {
|
||||
}
|
||||
|
||||
return {
|
||||
currentLimit: data?.currentLimit ?? DEFAULT_FREE_CREDITS,
|
||||
currentLimit: data?.currentLimit ?? getFreeTierLimit(),
|
||||
canEdit: data?.canEdit ?? false,
|
||||
minimumLimit: data?.minimumLimit ?? DEFAULT_FREE_CREDITS,
|
||||
minimumLimit: data?.minimumLimit ?? getFreeTierLimit(),
|
||||
plan: data?.plan ?? 'free',
|
||||
setBy: data?.setBy,
|
||||
updatedAt: data?.updatedAt ? new Date(data.updatedAt) : null,
|
||||
|
||||
@@ -1,27 +1,5 @@
|
||||
/**
|
||||
* Billing and cost constants shared between client and server code
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fallback free credits (in dollars) when env var is not set
|
||||
*/
|
||||
export const DEFAULT_FREE_CREDITS = 10
|
||||
|
||||
/**
|
||||
* Default per-user minimum limits (in dollars) for paid plans when env vars are absent
|
||||
*/
|
||||
export const DEFAULT_PRO_TIER_COST_LIMIT = 20
|
||||
export const DEFAULT_TEAM_TIER_COST_LIMIT = 40
|
||||
export const DEFAULT_ENTERPRISE_TIER_COST_LIMIT = 200
|
||||
|
||||
/**
|
||||
* Base charge applied to every workflow execution
|
||||
* This charge is applied regardless of whether the workflow uses AI models
|
||||
*/
|
||||
export const BASE_EXECUTION_CHARGE = 0.001
|
||||
|
||||
/**
|
||||
* Default threshold (in dollars) for incremental overage billing
|
||||
* When unbilled overage reaches this amount, an invoice item is created
|
||||
*/
|
||||
export const DEFAULT_OVERAGE_THRESHOLD = 50
|
||||
|
||||
@@ -4,15 +4,9 @@
|
||||
*/
|
||||
|
||||
import { db } from '@sim/db'
|
||||
import {
|
||||
DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB,
|
||||
DEFAULT_FREE_STORAGE_LIMIT_GB,
|
||||
DEFAULT_PRO_STORAGE_LIMIT_GB,
|
||||
DEFAULT_TEAM_STORAGE_LIMIT_GB,
|
||||
} from '@sim/db/consts'
|
||||
import { organization, subscription, userStats } from '@sim/db/schema'
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { env } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('StorageLimits')
|
||||
@@ -25,25 +19,16 @@ function gbToBytes(gb: number): number {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage limits from environment variables with fallback to constants
|
||||
* Get storage limits from environment variables
|
||||
* Returns limits in bytes
|
||||
* Defaults are defined in env.ts and will be applied automatically
|
||||
*/
|
||||
export function getStorageLimits() {
|
||||
return {
|
||||
free: gbToBytes(
|
||||
Number.parseInt(getEnv('FREE_STORAGE_LIMIT_GB') || String(DEFAULT_FREE_STORAGE_LIMIT_GB))
|
||||
),
|
||||
pro: gbToBytes(
|
||||
Number.parseInt(getEnv('PRO_STORAGE_LIMIT_GB') || String(DEFAULT_PRO_STORAGE_LIMIT_GB))
|
||||
),
|
||||
team: gbToBytes(
|
||||
Number.parseInt(getEnv('TEAM_STORAGE_LIMIT_GB') || String(DEFAULT_TEAM_STORAGE_LIMIT_GB))
|
||||
),
|
||||
enterpriseDefault: gbToBytes(
|
||||
Number.parseInt(
|
||||
getEnv('ENTERPRISE_STORAGE_LIMIT_GB') || String(DEFAULT_ENTERPRISE_STORAGE_LIMIT_GB)
|
||||
)
|
||||
),
|
||||
free: gbToBytes(env.FREE_STORAGE_LIMIT_GB),
|
||||
pro: gbToBytes(env.PRO_STORAGE_LIMIT_GB),
|
||||
team: gbToBytes(env.TEAM_STORAGE_LIMIT_GB),
|
||||
enterpriseDefault: gbToBytes(env.ENTERPRISE_STORAGE_LIMIT_GB),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +63,6 @@ export function getStorageLimitForPlan(plan: string, metadata?: any): number {
|
||||
*/
|
||||
export async function getUserStorageLimit(userId: string): Promise<number> {
|
||||
try {
|
||||
// Check if user is in a team/enterprise org
|
||||
const { getHighestPrioritySubscription } = await import('@/lib/billing/core/subscription')
|
||||
const sub = await getHighestPrioritySubscription(userId)
|
||||
|
||||
@@ -92,9 +76,7 @@ export async function getUserStorageLimit(userId: string): Promise<number> {
|
||||
return limits.pro
|
||||
}
|
||||
|
||||
// Team/Enterprise: Use organization limit
|
||||
if (sub.plan === 'team' || sub.plan === 'enterprise') {
|
||||
// Get organization storage limit
|
||||
const orgRecord = await db
|
||||
.select({ metadata: subscription.metadata })
|
||||
.from(subscription)
|
||||
@@ -108,7 +90,6 @@ export async function getUserStorageLimit(userId: string): Promise<number> {
|
||||
}
|
||||
}
|
||||
|
||||
// Default for team/enterprise
|
||||
return sub.plan === 'enterprise' ? limits.enterpriseDefault : limits.team
|
||||
}
|
||||
|
||||
@@ -125,12 +106,10 @@ export async function getUserStorageLimit(userId: string): Promise<number> {
|
||||
*/
|
||||
export async function getUserStorageUsage(userId: string): Promise<number> {
|
||||
try {
|
||||
// Check if user is in a team/enterprise org
|
||||
const { getHighestPrioritySubscription } = await import('@/lib/billing/core/subscription')
|
||||
const sub = await getHighestPrioritySubscription(userId)
|
||||
|
||||
if (sub && (sub.plan === 'team' || sub.plan === 'enterprise')) {
|
||||
// Use organization storage
|
||||
const orgRecord = await db
|
||||
.select({ storageUsedBytes: organization.storageUsedBytes })
|
||||
.from(organization)
|
||||
@@ -140,7 +119,6 @@ export async function getUserStorageUsage(userId: string): Promise<number> {
|
||||
return orgRecord.length > 0 ? orgRecord[0].storageUsedBytes || 0 : 0
|
||||
}
|
||||
|
||||
// Free/Pro: Use user stats
|
||||
const stats = await db
|
||||
.select({ storageUsedBytes: userStats.storageUsedBytes })
|
||||
.from(userStats)
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
import {
|
||||
DEFAULT_ENTERPRISE_TIER_COST_LIMIT,
|
||||
DEFAULT_FREE_CREDITS,
|
||||
DEFAULT_PRO_TIER_COST_LIMIT,
|
||||
DEFAULT_TEAM_TIER_COST_LIMIT,
|
||||
} from '@/lib/billing/constants'
|
||||
import { env } from '@/lib/env'
|
||||
|
||||
/**
|
||||
* Get the free tier limit from env or fallback to default
|
||||
* Get the free tier limit
|
||||
*/
|
||||
export function getFreeTierLimit(): number {
|
||||
return env.FREE_TIER_COST_LIMIT || DEFAULT_FREE_CREDITS
|
||||
return env.FREE_TIER_COST_LIMIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pro tier limit from env or fallback to default
|
||||
* Get the pro tier limit
|
||||
*/
|
||||
export function getProTierLimit(): number {
|
||||
return env.PRO_TIER_COST_LIMIT || DEFAULT_PRO_TIER_COST_LIMIT
|
||||
return env.PRO_TIER_COST_LIMIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the team tier limit per seat from env or fallback to default
|
||||
* Get the team tier limit per seat
|
||||
*/
|
||||
export function getTeamTierLimitPerSeat(): number {
|
||||
return env.TEAM_TIER_COST_LIMIT || DEFAULT_TEAM_TIER_COST_LIMIT
|
||||
return env.TEAM_TIER_COST_LIMIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enterprise tier limit per seat from env or fallback to default
|
||||
* Get the enterprise tier limit per seat
|
||||
*/
|
||||
export function getEnterpriseTierLimitPerSeat(): number {
|
||||
return env.ENTERPRISE_TIER_COST_LIMIT || DEFAULT_ENTERPRISE_TIER_COST_LIMIT
|
||||
return env.ENTERPRISE_TIER_COST_LIMIT
|
||||
}
|
||||
|
||||
export function checkEnterprisePlan(subscription: any): boolean {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { db } from '@sim/db'
|
||||
import { member, subscription, userStats } from '@sim/db/schema'
|
||||
import { and, eq, inArray, sql } from 'drizzle-orm'
|
||||
import type Stripe from 'stripe'
|
||||
import { DEFAULT_OVERAGE_THRESHOLD } from '@/lib/billing/constants'
|
||||
import { calculateSubscriptionOverage, getPlanPricing } from '@/lib/billing/core/billing'
|
||||
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
|
||||
import { requireStripeClient } from '@/lib/billing/stripe-client'
|
||||
@@ -11,7 +10,7 @@ import { createLogger } from '@/lib/logs/console/logger'
|
||||
|
||||
const logger = createLogger('ThresholdBilling')
|
||||
|
||||
const OVERAGE_THRESHOLD = env.OVERAGE_THRESHOLD_DOLLARS || DEFAULT_OVERAGE_THRESHOLD
|
||||
const OVERAGE_THRESHOLD = env.OVERAGE_THRESHOLD_DOLLARS
|
||||
|
||||
function parseDecimal(value: string | number | null | undefined): number {
|
||||
if (value === null || value === undefined) return 0
|
||||
|
||||
@@ -41,16 +41,16 @@ export const env = createEnv({
|
||||
STRIPE_SECRET_KEY: z.string().min(1).optional(), // Stripe secret key for payment processing
|
||||
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
|
||||
FREE_TIER_COST_LIMIT: z.number().optional().default(10), // Cost limit for free tier users (in dollars)
|
||||
FREE_STORAGE_LIMIT_GB: z.number().optional().default(5), // Storage limit in GB 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
|
||||
PRO_TIER_COST_LIMIT: z.number().optional().default(20), // Cost limit for pro tier users (in dollars)
|
||||
PRO_STORAGE_LIMIT_GB: z.number().optional().default(50), // Storage limit in GB 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
|
||||
TEAM_TIER_COST_LIMIT: z.number().optional().default(40), // Cost limit per seat for team tier (in dollars)
|
||||
TEAM_STORAGE_LIMIT_GB: z.number().optional().default(500), // Storage limit in GB for team tier organizations (pooled)
|
||||
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
|
||||
ENTERPRISE_TIER_COST_LIMIT: z.number().optional().default(200), // Cost limit per seat for enterprise tier (in dollars)
|
||||
ENTERPRISE_STORAGE_LIMIT_GB: z.number().optional().default(500), // Default storage limit in GB for enterprise tier (can be overridden per org)
|
||||
BILLING_ENABLED: z.boolean().optional(), // Enable billing enforcement and usage tracking
|
||||
OVERAGE_THRESHOLD_DOLLARS: z.number().optional().default(50), // Dollar threshold for incremental overage billing (default: $50)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { DEFAULT_FREE_CREDITS } from '@/lib/billing/constants'
|
||||
import { getFreeTierLimit } from '@/lib/billing/subscriptions/utils'
|
||||
import { createLogger } from '@/lib/logs/console/logger'
|
||||
import type {
|
||||
BillingStatus,
|
||||
@@ -16,7 +16,7 @@ const CACHE_DURATION = 30 * 1000
|
||||
|
||||
const defaultUsage: UsageData = {
|
||||
current: 0,
|
||||
limit: DEFAULT_FREE_CREDITS,
|
||||
limit: getFreeTierLimit(),
|
||||
percentUsed: 0,
|
||||
isWarning: false,
|
||||
isExceeded: false,
|
||||
|
||||
@@ -8,15 +8,6 @@
|
||||
*/
|
||||
export const DEFAULT_FREE_CREDITS = 10
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Tag slots available for knowledge base documents and embeddings
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user