From af742ad3ec1ccfee867e4a5cc088d6b58d8fd878 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Mon, 22 Sep 2025 10:43:13 -0700 Subject: [PATCH] fix(missing-user-stats): missing user stats rows covered via migration' --- apps/sim/lib/logs/execution/logger.ts | 55 ++++++++----------- .../migrations/0091_backfill_user_stats.sql | 47 ++++++++++++++++ 2 files changed, 69 insertions(+), 33 deletions(-) create mode 100644 packages/db/migrations/0091_backfill_user_stats.sql diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 9abbad12f7..7bfefb6723 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -403,54 +403,43 @@ export class ExecutionLogger implements IExecutionLoggerService { // Apply cost multiplier only to model costs, not base execution charge const costToStore = costSummary.baseExecutionCharge + costSummary.modelCost * costMultiplier - // Upsert user stats record - insert if doesn't exist, update if it does - const { getFreeTierLimit } = await import('@/lib/billing/subscriptions/utils') - const defaultLimit = getFreeTierLimit() + const existing = await db.select().from(userStats).where(eq(userStats.userId, userId)) + if (existing.length === 0) { + logger.error('User stats record not found - should be created during onboarding', { + userId, + trigger, + }) + return + } + + const updateFields: any = { + totalTokensUsed: sql`total_tokens_used + ${costSummary.totalTokens}`, + totalCost: sql`total_cost + ${costToStore}`, + currentPeriodCost: sql`current_period_cost + ${costToStore}`, + lastActive: new Date(), + } - const triggerIncrements: any = {} switch (trigger) { case 'manual': - triggerIncrements.totalManualExecutions = sql`total_manual_executions + 1` + updateFields.totalManualExecutions = sql`total_manual_executions + 1` break case 'api': - triggerIncrements.totalApiCalls = sql`total_api_calls + 1` + updateFields.totalApiCalls = sql`total_api_calls + 1` break case 'webhook': - triggerIncrements.totalWebhookTriggers = sql`total_webhook_triggers + 1` + updateFields.totalWebhookTriggers = sql`total_webhook_triggers + 1` break case 'schedule': - triggerIncrements.totalScheduledExecutions = sql`total_scheduled_executions + 1` + updateFields.totalScheduledExecutions = sql`total_scheduled_executions + 1` break case 'chat': - triggerIncrements.totalChatExecutions = sql`total_chat_executions + 1` + updateFields.totalChatExecutions = sql`total_chat_executions + 1` break } - await db - .insert(userStats) - .values({ - id: uuidv4(), - userId: userId, - currentUsageLimit: defaultLimit.toString(), - usageLimitUpdatedAt: new Date(), - totalTokensUsed: costSummary.totalTokens, - totalCost: costToStore, - currentPeriodCost: costToStore, - lastActive: new Date(), - ...triggerIncrements, - }) - .onConflictDoUpdate({ - target: userStats.userId, - set: { - totalTokensUsed: sql`total_tokens_used + ${costSummary.totalTokens}`, - totalCost: sql`total_cost + ${costToStore}`, - currentPeriodCost: sql`current_period_cost + ${costToStore}`, - lastActive: new Date(), - ...triggerIncrements, - }, - }) + await db.update(userStats).set(updateFields).where(eq(userStats.userId, userId)) - logger.debug('Upserted user stats record with cost data', { + logger.debug('Updated user stats record with cost data', { userId, trigger, addedCost: costToStore, diff --git a/packages/db/migrations/0091_backfill_user_stats.sql b/packages/db/migrations/0091_backfill_user_stats.sql new file mode 100644 index 0000000000..da51eb41e9 --- /dev/null +++ b/packages/db/migrations/0091_backfill_user_stats.sql @@ -0,0 +1,47 @@ +-- Backfill user_stats for any users missing a stats row +-- Uses defaults from schema for limits and counters + +INSERT INTO "user_stats" ( + "id", + "user_id", + "current_usage_limit", + "usage_limit_updated_at", + "total_manual_executions", + "total_api_calls", + "total_webhook_triggers", + "total_scheduled_executions", + "total_chat_executions", + "total_tokens_used", + "total_cost", + "current_period_cost", + "last_period_cost", + "total_copilot_cost", + "total_copilot_tokens", + "total_copilot_calls", + "last_active", + "billing_blocked" +) +SELECT + u."id" AS id, + u."id" AS user_id, + NULL::decimal AS current_usage_limit, + NOW() AS usage_limit_updated_at, + 0 AS total_manual_executions, + 0 AS total_api_calls, + 0 AS total_webhook_triggers, + 0 AS total_scheduled_executions, + 0 AS total_chat_executions, + 0 AS total_tokens_used, + '0'::decimal AS total_cost, + '0'::decimal AS current_period_cost, + '0'::decimal AS last_period_cost, + '0'::decimal AS total_copilot_cost, + 0 AS total_copilot_tokens, + 0 AS total_copilot_calls, + NOW() AS last_active, + FALSE AS billing_blocked +FROM "user" u +LEFT JOIN "user_stats" s ON s."user_id" = u."id" +WHERE s."user_id" IS NULL; + +