( userId: string, bytes: number, workspaceId?: string )
| 69 | * (80% / 100%) after the increment. Best-effort; never blocks the upload. |
| 70 | */ |
| 71 | export async function incrementStorageUsage( |
| 72 | userId: string, |
| 73 | bytes: number, |
| 74 | workspaceId?: string |
| 75 | ): Promise<void> { |
| 76 | if (!isBillingEnabled) { |
| 77 | logger.debug('Billing disabled, skipping storage increment') |
| 78 | return |
| 79 | } |
| 80 | |
| 81 | let sub: HighestPrioritySubscription | null = null |
| 82 | try { |
| 83 | const { getHighestPrioritySubscription } = await import('@/lib/billing/core/subscription') |
| 84 | sub = await getHighestPrioritySubscription(userId) |
| 85 | |
| 86 | // Org-scoped subs pool at the org level; personal plans per-user. |
| 87 | if (isOrgScopedSubscription(sub, userId) && sub) { |
| 88 | await db |
| 89 | .update(organization) |
| 90 | .set({ |
| 91 | storageUsedBytes: sql`${organization.storageUsedBytes} + ${bytes}`, |
| 92 | }) |
| 93 | .where(eq(organization.id, sub.referenceId)) |
| 94 | |
| 95 | logger.info(`Incremented org storage: ${bytes} bytes for org ${sub.referenceId}`) |
| 96 | } else { |
| 97 | await db |
| 98 | .update(userStats) |
| 99 | .set({ |
| 100 | storageUsedBytes: sql`${userStats.storageUsedBytes} + ${bytes}`, |
| 101 | }) |
| 102 | .where(eq(userStats.userId, userId)) |
| 103 | |
| 104 | logger.info(`Incremented user storage: ${bytes} bytes for user ${userId}`) |
| 105 | } |
| 106 | } catch (error) { |
| 107 | logger.error('Error incrementing storage usage:', error) |
| 108 | throw error |
| 109 | } |
| 110 | |
| 111 | if (workspaceId) { |
| 112 | void maybeNotifyStorageLimit(userId, workspaceId, sub) |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Decrement storage usage after file deletion |
no test coverage detected