@lastbrain/module-ai 2.0.26 → 2.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +508 -9
- package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
- package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models/[id].js +58 -0
- package/dist/api/admin/ai-provider-models.d.ts +20 -0
- package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models.js +26 -0
- package/dist/api/admin/ai-providers/[key].d.ts +18 -0
- package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
- package/dist/api/admin/ai-providers/[key].js +55 -0
- package/dist/api/admin/ai-providers.d.ts +20 -0
- package/dist/api/admin/ai-providers.d.ts.map +1 -0
- package/dist/api/admin/ai-providers.js +26 -0
- package/dist/api/admin/billing-analytics.d.ts +43 -0
- package/dist/api/admin/billing-analytics.d.ts.map +1 -0
- package/dist/api/admin/billing-analytics.js +144 -0
- package/dist/api/admin/global-ai-settings.d.ts +14 -0
- package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
- package/dist/api/admin/global-ai-settings.js +63 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
- package/dist/api/admin/token-packs/[id].js +3 -2
- package/dist/api/admin/token-packs.d.ts.map +1 -1
- package/dist/api/admin/token-packs.js +3 -2
- package/dist/api/admin/user-monthly-details.d.ts +49 -0
- package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
- package/dist/api/admin/user-monthly-details.js +140 -0
- package/dist/api/admin/user-quota.d.ts +21 -0
- package/dist/api/admin/user-quota.d.ts.map +1 -0
- package/dist/api/admin/user-quota.js +59 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -1
- package/dist/api/admin/user-token/[id].js +2 -1
- package/dist/api/admin/user-token.d.ts +5 -2
- package/dist/api/admin/user-token.d.ts.map +1 -1
- package/dist/api/admin/user-token.js +91 -17
- package/dist/api/admin/user-usage-by-model.d.ts +22 -0
- package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
- package/dist/api/admin/user-usage-by-model.js +78 -0
- package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
- package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
- package/dist/api/admin/user-wallet-analytics.js +67 -0
- package/dist/api/admin/wallet-repair/route.d.ts +30 -0
- package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
- package/dist/api/admin/wallet-repair/route.js +63 -0
- package/dist/api/auth/ai-model-settings.d.ts +21 -0
- package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-model-settings.js +86 -0
- package/dist/api/auth/ai-settings.d.ts +17 -0
- package/dist/api/auth/ai-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-settings.js +87 -0
- package/dist/api/auth/api-keys/[id].d.ts +17 -0
- package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
- package/dist/api/auth/api-keys/[id].js +66 -0
- package/dist/api/auth/api-keys.d.ts +19 -0
- package/dist/api/auth/api-keys.d.ts.map +1 -0
- package/dist/api/auth/api-keys.js +94 -0
- package/dist/api/auth/create-checkout.d.ts +1 -1
- package/dist/api/auth/create-checkout.d.ts.map +1 -1
- package/dist/api/auth/create-checkout.js +8 -6
- package/dist/api/auth/generate-image.d.ts +2 -2
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +404 -104
- package/dist/api/auth/generate-text.d.ts +3 -2
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +130 -58
- package/dist/api/auth/process-ocr.d.ts +9 -0
- package/dist/api/auth/process-ocr.d.ts.map +1 -0
- package/dist/api/auth/process-ocr.js +43 -0
- package/dist/api/auth/prompts/stats.d.ts +14 -0
- package/dist/api/auth/prompts/stats.d.ts.map +1 -0
- package/dist/api/auth/prompts/stats.js +46 -0
- package/dist/api/auth/prompts.d.ts +26 -0
- package/dist/api/auth/prompts.d.ts.map +1 -0
- package/dist/api/auth/prompts.js +175 -0
- package/dist/api/auth/token-balance.d.ts +26 -0
- package/dist/api/auth/token-balance.d.ts.map +1 -0
- package/dist/api/auth/token-balance.js +47 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -1
- package/dist/api/auth/token-checkout.js +22 -4
- package/dist/api/auth/token-packs.d.ts.map +1 -1
- package/dist/api/auth/token-packs.js +2 -1
- package/dist/api/auth/usage-by-model.d.ts +25 -0
- package/dist/api/auth/usage-by-model.d.ts.map +1 -0
- package/dist/api/auth/usage-by-model.js +95 -0
- package/dist/api/auth/usage.d.ts +26 -0
- package/dist/api/auth/usage.d.ts.map +1 -0
- package/dist/api/auth/usage.js +127 -0
- package/dist/api/auth/user-tokens.d.ts.map +1 -1
- package/dist/api/auth/user-tokens.js +36 -2
- package/dist/api/auth/wallet/route.d.ts +17 -0
- package/dist/api/auth/wallet/route.d.ts.map +1 -0
- package/dist/api/auth/wallet/route.js +68 -0
- package/dist/api/auth/wallet.d.ts +16 -0
- package/dist/api/auth/wallet.d.ts.map +1 -0
- package/dist/api/auth/wallet.js +71 -0
- package/dist/api/public/gateway-models.d.ts +25 -0
- package/dist/api/public/gateway-models.d.ts.map +1 -0
- package/dist/api/public/gateway-models.js +49 -0
- package/dist/api/public/pricing-summary.d.ts +46 -0
- package/dist/api/public/pricing-summary.d.ts.map +1 -0
- package/dist/api/public/pricing-summary.js +70 -0
- package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
- package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/stats.js +37 -0
- package/dist/api/public/prompts/[id]/view.d.ts +15 -0
- package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/view.js +41 -0
- package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
- package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
- package/dist/api/public/prompts/slug/[slug].js +40 -0
- package/dist/api/public/prompts/user/[username].d.ts +17 -0
- package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
- package/dist/api/public/prompts/user/[username].js +51 -0
- package/dist/api/public/prompts.d.ts +15 -0
- package/dist/api/public/prompts.d.ts.map +1 -0
- package/dist/api/public/prompts.js +45 -0
- package/dist/api/public/token-packs.d.ts +11 -0
- package/dist/api/public/token-packs.d.ts.map +1 -0
- package/dist/api/public/token-packs.js +25 -0
- package/dist/api/public/token-pricing.d.ts +44 -0
- package/dist/api/public/token-pricing.d.ts.map +1 -0
- package/dist/api/public/token-pricing.js +168 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/artifacts.js +217 -0
- package/dist/api/public/v1/_lib/auth.d.ts +43 -0
- package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/auth.js +108 -0
- package/dist/api/public/v1/_lib/errors.d.ts +17 -0
- package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/errors.js +16 -0
- package/dist/api/public/v1/_lib/log.d.ts +29 -0
- package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/log.js +68 -0
- package/dist/api/public/v1/_lib/quota.d.ts +24 -0
- package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/quota.js +118 -0
- package/dist/api/public/v1/_lib/router.d.ts +54 -0
- package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/router.js +119 -0
- package/dist/api/public/v1/connect.d.ts +20 -0
- package/dist/api/public/v1/connect.d.ts.map +1 -0
- package/dist/api/public/v1/connect.js +119 -0
- package/dist/api/public/v1/doc.d.ts +239 -0
- package/dist/api/public/v1/doc.d.ts.map +1 -0
- package/dist/api/public/v1/doc.js +253 -0
- package/dist/api/public/v1/history/[id].d.ts +92 -0
- package/dist/api/public/v1/history/[id].d.ts.map +1 -0
- package/dist/api/public/v1/history/[id].js +176 -0
- package/dist/api/public/v1/history.d.ts +30 -0
- package/dist/api/public/v1/history.d.ts.map +1 -0
- package/dist/api/public/v1/history.js +142 -0
- package/dist/api/public/v1/image-ai.d.ts +24 -0
- package/dist/api/public/v1/image-ai.d.ts.map +1 -0
- package/dist/api/public/v1/image-ai.js +233 -0
- package/dist/api/public/v1/prompts.d.ts +19 -0
- package/dist/api/public/v1/prompts.d.ts.map +1 -0
- package/dist/api/public/v1/prompts.js +107 -0
- package/dist/api/public/v1/provider.d.ts +16 -0
- package/dist/api/public/v1/provider.d.ts.map +1 -0
- package/dist/api/public/v1/provider.js +130 -0
- package/dist/api/public/v1/purchase.d.ts +11 -0
- package/dist/api/public/v1/purchase.d.ts.map +1 -0
- package/dist/api/public/v1/purchase.js +18 -0
- package/dist/api/public/v1/status.d.ts +35 -0
- package/dist/api/public/v1/status.d.ts.map +1 -0
- package/dist/api/public/v1/status.js +163 -0
- package/dist/api/public/v1/text-ai.d.ts +26 -0
- package/dist/api/public/v1/text-ai.d.ts.map +1 -0
- package/dist/api/public/v1/text-ai.js +239 -0
- package/dist/api/public/webhook.d.ts.map +1 -1
- package/dist/api/public/webhook.js +50 -39
- package/dist/api/track-usage.d.ts +12 -0
- package/dist/api/track-usage.d.ts.map +1 -0
- package/dist/api/track-usage.js +37 -0
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/DocUsageCustom.js +6 -6
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
- package/dist/components/admin/UserTokenTab.js +170 -23
- package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
- package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
- package/dist/components/auth/AuthDashboardAi.js +53 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
- package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
- package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
- package/dist/server/__tests__/billing.test.d.ts +5 -0
- package/dist/server/__tests__/billing.test.d.ts.map +1 -0
- package/dist/server/__tests__/billing.test.js +223 -0
- package/dist/server/ai-client.d.ts +59 -0
- package/dist/server/ai-client.d.ts.map +1 -0
- package/dist/server/ai-client.js +111 -0
- package/dist/server/ai-generation-service.d.ts +66 -0
- package/dist/server/ai-generation-service.d.ts.map +1 -0
- package/dist/server/ai-generation-service.js +274 -0
- package/dist/server/billing.d.ts +200 -0
- package/dist/server/billing.d.ts.map +1 -0
- package/dist/server/billing.js +488 -0
- package/dist/server/gateway-service.d.ts +13 -0
- package/dist/server/gateway-service.d.ts.map +1 -0
- package/dist/server/gateway-service.js +161 -0
- package/dist/server/global-settings.d.ts +16 -0
- package/dist/server/global-settings.d.ts.map +1 -0
- package/dist/server/global-settings.js +42 -0
- package/dist/server/model-filter.d.ts +25 -0
- package/dist/server/model-filter.d.ts.map +1 -0
- package/dist/server/model-filter.js +240 -0
- package/dist/server/ocr.d.ts +39 -0
- package/dist/server/ocr.d.ts.map +1 -0
- package/dist/server/ocr.js +280 -0
- package/dist/server/openai-client.d.ts +19 -0
- package/dist/server/openai-client.d.ts.map +1 -0
- package/dist/server/openai-client.js +26 -0
- package/dist/server/pricing-config.d.ts +18 -0
- package/dist/server/pricing-config.d.ts.map +1 -0
- package/dist/server/pricing-config.js +94 -0
- package/dist/server/pricing-validator.d.ts +41 -0
- package/dist/server/pricing-validator.d.ts.map +1 -0
- package/dist/server/pricing-validator.js +113 -0
- package/dist/server/pricing.d.ts +121 -0
- package/dist/server/pricing.d.ts.map +1 -0
- package/dist/server/pricing.js +225 -0
- package/dist/server/quota.d.ts +66 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +538 -0
- package/dist/server/wallet-repair.d.ts +32 -0
- package/dist/server/wallet-repair.d.ts.map +1 -0
- package/dist/server/wallet-repair.js +189 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +87 -16
- package/dist/sitemap/handlers/prompts.d.ts +6 -0
- package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
- package/dist/sitemap/handlers/prompts.js +72 -0
- package/dist/sitemap/handlers/users.d.ts +6 -0
- package/dist/sitemap/handlers/users.d.ts.map +1 -0
- package/dist/sitemap/handlers/users.js +80 -0
- package/dist/sitemap/manifest.d.ts +8 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +27 -0
- package/dist/types/gateway.d.ts +40 -0
- package/dist/types/gateway.d.ts.map +1 -0
- package/dist/types/gateway.js +4 -0
- package/dist/types/quota.d.ts +74 -0
- package/dist/types/quota.d.ts.map +1 -0
- package/dist/types/quota.js +11 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +17 -0
- package/dist/web/admin/AdminTokenPacksPage.js +1 -1
- package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
- package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
- package/dist/web/admin/BillingAnalyticsPage.js +141 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
- package/dist/web/admin/GlobalAISettingsPage.js +93 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +20 -7
- package/dist/web/auth/AISettingsPage.d.ts +2 -0
- package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
- package/dist/web/auth/AISettingsPage.js +258 -0
- package/dist/web/auth/APIKeysPage.d.ts +2 -0
- package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
- package/dist/web/auth/APIKeysPage.js +154 -0
- package/dist/web/auth/HistoryPage.d.ts +2 -0
- package/dist/web/auth/HistoryPage.d.ts.map +1 -0
- package/dist/web/auth/HistoryPage.js +279 -0
- package/dist/web/auth/PromptsPage.d.ts +5 -0
- package/dist/web/auth/PromptsPage.d.ts.map +1 -0
- package/dist/web/auth/PromptsPage.js +137 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +88 -31
- package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
- package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
- package/dist/web/auth/UsageAndTokensPage.js +157 -0
- package/dist/web/auth/UsagePage.d.ts +2 -0
- package/dist/web/auth/UsagePage.d.ts.map +1 -0
- package/dist/web/auth/UsagePage.js +62 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
- package/dist/web/auth/components/ModelUsageTable.js +37 -0
- package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
- package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
- package/dist/web/auth/components/PurchaseButton.js +13 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
- package/dist/web/auth/components/TokenHistoryCard.js +76 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
- package/dist/web/auth/components/TokenKpiGrid.js +38 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByDayChart.js +32 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
- package/dist/web/auth/components/WalletStatusCard.js +50 -0
- package/dist/web/components/ImageGenerative.d.ts +3 -1
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +139 -52
- package/dist/web/components/TextareaGenerative.d.ts +3 -1
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +10 -5
- package/dist/web/public/PromptDetailPage.d.ts +25 -0
- package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPage.js +71 -0
- package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
- package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPageServer.js +68 -0
- package/dist/web/public/PublicPromptsPage.d.ts +5 -0
- package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
- package/dist/web/public/PublicPromptsPage.js +110 -0
- package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
- package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
- package/dist/web/public/PurchaseTokensPage.js +98 -0
- package/dist/web/public/UserAvatar.d.ts +13 -0
- package/dist/web/public/UserAvatar.d.ts.map +1 -0
- package/dist/web/public/UserAvatar.js +13 -0
- package/dist/web/public/UserDetailPageServer.d.ts +15 -0
- package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/UserDetailPageServer.js +31 -0
- package/dist/web/public/UserPromptsPage.d.ts +9 -0
- package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPage.js +112 -0
- package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
- package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPageServer.js +31 -0
- package/package.json +18 -9
- package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
- package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
- package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
- package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
- package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
- package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
- package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
- package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
- package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
- package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
- package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
- package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
- package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
- package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
- package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
- package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
- package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
- package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
- package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
- package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
- package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
- package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
- package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
- package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
- package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side quota management functions
|
|
3
|
+
* Handles monthly token quota (separate from purchased tokens)
|
|
4
|
+
*/
|
|
5
|
+
import type { TokenQuotaStatus, DebitTokensResponse, UpdateQuotaRequest } from "../types/quota";
|
|
6
|
+
/**
|
|
7
|
+
* Get user's current quota status
|
|
8
|
+
*/
|
|
9
|
+
export declare function getUserQuotaStatus(userId: string): Promise<TokenQuotaStatus>;
|
|
10
|
+
/**
|
|
11
|
+
* Debit tokens with priority: quota first, then purchased
|
|
12
|
+
* This is the CENTRAL function for token consumption
|
|
13
|
+
*/
|
|
14
|
+
export declare function debitTokensWithPriority(userId: string, tokensToDebit: number, model?: string, prompt?: string, meta?: Record<string, unknown>): Promise<DebitTokensResponse>;
|
|
15
|
+
/**
|
|
16
|
+
* Update or create user quota (for subscription updates/renewals)
|
|
17
|
+
*/
|
|
18
|
+
export declare function updateUserQuota(request: UpdateQuotaRequest): Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}>;
|
|
22
|
+
/**
|
|
23
|
+
* Handle subscription update from billing-pro webhook
|
|
24
|
+
* This function is called by billing-pro when subscription events occur
|
|
25
|
+
*
|
|
26
|
+
* @param params Subscription parameters from Stripe
|
|
27
|
+
* @returns Success status
|
|
28
|
+
*/
|
|
29
|
+
export declare function handleSubscriptionQuotaUpdate(params: {
|
|
30
|
+
owner_id: string;
|
|
31
|
+
plan_slug: string;
|
|
32
|
+
subscription_id: string | null;
|
|
33
|
+
current_period_start: string;
|
|
34
|
+
current_period_end: string;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
success: boolean;
|
|
37
|
+
error?: string;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Update custom quota for a user (admin override)
|
|
41
|
+
*/
|
|
42
|
+
export declare function updateCustomQuota(userId: string, customTokens: number | null): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Get quota usage history for a user
|
|
48
|
+
*/
|
|
49
|
+
export declare function getQuotaUsageHistory(userId: string, limit?: number, offset?: number): Promise<any[]>;
|
|
50
|
+
/**
|
|
51
|
+
* Refund/credit tokens back to user in case of error after debit
|
|
52
|
+
* This ensures users don't lose tokens if the operation fails after AI generation
|
|
53
|
+
*
|
|
54
|
+
* @param userId User ID
|
|
55
|
+
* @param tokensToRefund Number of tokens to credit back
|
|
56
|
+
* @param model Model that was used (for logging)
|
|
57
|
+
* @param reason Reason for the refund (for logging)
|
|
58
|
+
* @returns Success status with refund details
|
|
59
|
+
*/
|
|
60
|
+
export declare function refundTokens(userId: string, tokensToRefund: number, model: string, reason: string): Promise<{
|
|
61
|
+
success: boolean;
|
|
62
|
+
error?: string;
|
|
63
|
+
refundedToQuota: number;
|
|
64
|
+
refundedToPurchased: number;
|
|
65
|
+
}>;
|
|
66
|
+
//# sourceMappingURL=quota.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../../src/server/quota.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAEV,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAWxB;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CA0E3B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA+Q9B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwC/C;AAED;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DhD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0C/C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,kBAkBnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC,CAgHD"}
|
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side quota management functions
|
|
3
|
+
* Handles monthly token quota (separate from purchased tokens)
|
|
4
|
+
*/
|
|
5
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
6
|
+
import { logger } from "@lastbrain/core";
|
|
7
|
+
// Import default quotas
|
|
8
|
+
const PLAN_DEFAULTS = {
|
|
9
|
+
free: 0,
|
|
10
|
+
pro: 50000,
|
|
11
|
+
premium: 100000,
|
|
12
|
+
enterprise: 150000,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Get user's current quota status
|
|
16
|
+
*/
|
|
17
|
+
export async function getUserQuotaStatus(userId) {
|
|
18
|
+
const supabase = await getSupabaseServiceClient();
|
|
19
|
+
try {
|
|
20
|
+
// Get the most recent quota record (not .single() to avoid PGRST116 error)
|
|
21
|
+
const { data, error } = await supabase
|
|
22
|
+
.from("user_token_quota_monthly")
|
|
23
|
+
.select("*")
|
|
24
|
+
.eq("owner_id", userId)
|
|
25
|
+
.order("created_at", { ascending: false })
|
|
26
|
+
.limit(1);
|
|
27
|
+
if (error) {
|
|
28
|
+
logger.debug(`[getUserQuotaStatus] Query error: ${error.code} - ${error.message}`);
|
|
29
|
+
}
|
|
30
|
+
// Take the first (most recent) record
|
|
31
|
+
const record = data?.[0];
|
|
32
|
+
if (!record) {
|
|
33
|
+
return {
|
|
34
|
+
hasQuota: false,
|
|
35
|
+
plan: "free",
|
|
36
|
+
effectiveQuota: 0,
|
|
37
|
+
usedQuota: 0,
|
|
38
|
+
remainingQuota: 0,
|
|
39
|
+
periodStart: null,
|
|
40
|
+
periodEnd: null,
|
|
41
|
+
isActive: false,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const quota = record;
|
|
45
|
+
const now = new Date();
|
|
46
|
+
const periodStart = quota.period_start
|
|
47
|
+
? new Date(quota.period_start)
|
|
48
|
+
: null;
|
|
49
|
+
const periodEnd = quota.period_end ? new Date(quota.period_end) : null;
|
|
50
|
+
// Check if within period
|
|
51
|
+
const isActive = periodStart && periodEnd && now >= periodStart && now <= periodEnd;
|
|
52
|
+
const result = {
|
|
53
|
+
hasQuota: quota.effective_included_tokens > 0,
|
|
54
|
+
plan: quota.plan,
|
|
55
|
+
effectiveQuota: quota.effective_included_tokens,
|
|
56
|
+
usedQuota: quota.used_included_tokens,
|
|
57
|
+
remainingQuota: Math.max(0, quota.effective_included_tokens - quota.used_included_tokens),
|
|
58
|
+
periodStart: quota.period_start,
|
|
59
|
+
periodEnd: quota.period_end,
|
|
60
|
+
isActive: !!isActive,
|
|
61
|
+
};
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger.error("[getUserQuotaStatus] Error:", error);
|
|
66
|
+
return {
|
|
67
|
+
hasQuota: false,
|
|
68
|
+
plan: "free",
|
|
69
|
+
effectiveQuota: 0,
|
|
70
|
+
usedQuota: 0,
|
|
71
|
+
remainingQuota: 0,
|
|
72
|
+
periodStart: null,
|
|
73
|
+
periodEnd: null,
|
|
74
|
+
isActive: false,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Debit tokens with priority: quota first, then purchased
|
|
80
|
+
* This is the CENTRAL function for token consumption
|
|
81
|
+
*/
|
|
82
|
+
export async function debitTokensWithPriority(userId, tokensToDebit, model, prompt, meta = {}) {
|
|
83
|
+
logger.debug(`[debitTokensWithPriority] START for user ${userId}: tokensToDebit=${tokensToDebit}, model=${model}`);
|
|
84
|
+
if (tokensToDebit <= 0) {
|
|
85
|
+
logger.warn(`[debitTokensWithPriority] Invalid amount: ${tokensToDebit}`);
|
|
86
|
+
return {
|
|
87
|
+
success: false,
|
|
88
|
+
debitedFromQuota: 0,
|
|
89
|
+
debitedFromPurchased: 0,
|
|
90
|
+
remainingQuota: 0,
|
|
91
|
+
remainingPurchased: 0,
|
|
92
|
+
error: "Le montant doit être positif",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const supabase = await getSupabaseServiceClient();
|
|
96
|
+
try {
|
|
97
|
+
// Step 1: Get quota status
|
|
98
|
+
const quotaStatus = await getUserQuotaStatus(userId);
|
|
99
|
+
logger.debug(`[debitTokensWithPriority] Quota status:`, {
|
|
100
|
+
hasQuota: quotaStatus.hasQuota,
|
|
101
|
+
plan: quotaStatus.plan,
|
|
102
|
+
effectiveQuota: quotaStatus.effectiveQuota,
|
|
103
|
+
usedQuota: quotaStatus.usedQuota,
|
|
104
|
+
remainingQuota: quotaStatus.remainingQuota,
|
|
105
|
+
isActive: quotaStatus.isActive,
|
|
106
|
+
periodStart: quotaStatus.periodStart,
|
|
107
|
+
periodEnd: quotaStatus.periodEnd,
|
|
108
|
+
});
|
|
109
|
+
let debitedFromQuota = 0;
|
|
110
|
+
let debitedFromPurchased = 0;
|
|
111
|
+
let tokensRemaining = tokensToDebit;
|
|
112
|
+
// Step 2: Try to debit from quota first (if active and available)
|
|
113
|
+
if (quotaStatus.isActive && quotaStatus.remainingQuota > 0) {
|
|
114
|
+
debitedFromQuota = Math.min(tokensRemaining, quotaStatus.remainingQuota);
|
|
115
|
+
tokensRemaining -= debitedFromQuota;
|
|
116
|
+
logger.debug(`[debitTokensWithPriority] Debiting from quota: ${debitedFromQuota} tokens (remaining to debit: ${tokensRemaining})`);
|
|
117
|
+
// Update quota usage
|
|
118
|
+
const { data: quotaData, error: quotaError } = await supabase
|
|
119
|
+
.from("user_token_quota_monthly")
|
|
120
|
+
.update({
|
|
121
|
+
used_included_tokens: quotaStatus.usedQuota + debitedFromQuota,
|
|
122
|
+
})
|
|
123
|
+
.eq("owner_id", userId)
|
|
124
|
+
.select()
|
|
125
|
+
.single();
|
|
126
|
+
if (quotaError) {
|
|
127
|
+
logger.error("[debitTokensWithPriority] Quota update error:", quotaError);
|
|
128
|
+
throw new Error("Erreur lors de la mise à jour du quota");
|
|
129
|
+
}
|
|
130
|
+
logger.debug(`[debitTokensWithPriority] Quota updated successfully:`, quotaData);
|
|
131
|
+
// Log quota usage
|
|
132
|
+
if (debitedFromQuota > 0 &&
|
|
133
|
+
quotaStatus.periodStart &&
|
|
134
|
+
quotaStatus.periodEnd) {
|
|
135
|
+
// Truncate prompt to 32 characters for GDPR compliance
|
|
136
|
+
const truncatedPrompt = prompt ? prompt.substring(0, 32) : null;
|
|
137
|
+
logger.debug(`[debitTokensWithPriority] Inserting to user_token_quota_ledger:`, {
|
|
138
|
+
owner_id: userId,
|
|
139
|
+
tokens_used: debitedFromQuota,
|
|
140
|
+
model,
|
|
141
|
+
period_start: quotaStatus.periodStart,
|
|
142
|
+
period_end: quotaStatus.periodEnd,
|
|
143
|
+
});
|
|
144
|
+
const { data: ledgerData, error: ledgerInsertError } = await supabase
|
|
145
|
+
.from("user_token_quota_ledger")
|
|
146
|
+
.insert({
|
|
147
|
+
owner_id: userId,
|
|
148
|
+
tokens_used: debitedFromQuota,
|
|
149
|
+
model,
|
|
150
|
+
prompt: truncatedPrompt,
|
|
151
|
+
period_start: quotaStatus.periodStart,
|
|
152
|
+
period_end: quotaStatus.periodEnd,
|
|
153
|
+
meta,
|
|
154
|
+
})
|
|
155
|
+
.select();
|
|
156
|
+
if (ledgerInsertError) {
|
|
157
|
+
logger.error("[debitTokensWithPriority] Ledger insert error:", ledgerInsertError);
|
|
158
|
+
throw new Error("Erreur lors de l'insertion en ledger quota");
|
|
159
|
+
}
|
|
160
|
+
logger.debug(`[debitTokensWithPriority] Ledger insertion successful:`, ledgerData);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
logger.debug(`[debitTokensWithPriority] Skipped ledger insertion: debitedFromQuota=${debitedFromQuota}, periodStart=${quotaStatus.periodStart}, periodEnd=${quotaStatus.periodEnd}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
logger.debug(`[debitTokensWithPriority] Skipped quota debit: isActive=${quotaStatus.isActive}, remainingQuota=${quotaStatus.remainingQuota}`);
|
|
168
|
+
}
|
|
169
|
+
// Step 3: Debit remaining from purchased tokens (if any)
|
|
170
|
+
if (tokensRemaining > 0) {
|
|
171
|
+
logger.debug(`[debitTokensWithPriority] Need to debit from purchased: ${tokensRemaining} tokens`);
|
|
172
|
+
// Check purchased balance
|
|
173
|
+
const { data: balanceData } = await supabase
|
|
174
|
+
.from("user_token_balance_v")
|
|
175
|
+
.select("balance")
|
|
176
|
+
.eq("owner_id", userId)
|
|
177
|
+
.single();
|
|
178
|
+
const purchasedBalance = balanceData?.balance || 0;
|
|
179
|
+
logger.debug(`[debitTokensWithPriority] Purchased balance: ${purchasedBalance}`);
|
|
180
|
+
if (purchasedBalance < tokensRemaining) {
|
|
181
|
+
logger.warn(`[debitTokensWithPriority] Purchased tokens insufficient: balance=${purchasedBalance}, required=${tokensRemaining}`);
|
|
182
|
+
// If the caller explicitly allows an overdraft (one-time negative balance), proceed
|
|
183
|
+
const allowOverdraft = Boolean(meta?.allowOverdraft);
|
|
184
|
+
if (!allowOverdraft) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
debitedFromQuota,
|
|
188
|
+
debitedFromPurchased: 0,
|
|
189
|
+
remainingQuota: quotaStatus.remainingQuota - debitedFromQuota,
|
|
190
|
+
remainingPurchased: purchasedBalance,
|
|
191
|
+
error: `Tokens insuffisants. Quota: ${quotaStatus.remainingQuota}, Achetés: ${purchasedBalance}, Requis: ${tokensToDebit}`,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
logger.debug(`[debitTokensWithPriority] allowOverdraft=true, proceeding to insert negative ledger entry`);
|
|
195
|
+
// continue and insert the ledger entry below which will make the balance negative
|
|
196
|
+
}
|
|
197
|
+
// Debit from purchased
|
|
198
|
+
logger.debug(`[debitTokensWithPriority] Inserting to user_token_ledger: ${tokensRemaining} tokens`);
|
|
199
|
+
// Truncate prompt to 32 characters for GDPR compliance
|
|
200
|
+
const truncatedPrompt = prompt ? prompt.substring(0, 32) : null;
|
|
201
|
+
// Insert ledger entry for purchased tokens. Use try/catch and fallback
|
|
202
|
+
// to a minimal insert if the full insert fails (to avoid blocking generation).
|
|
203
|
+
let insertData = null;
|
|
204
|
+
try {
|
|
205
|
+
const res = await supabase
|
|
206
|
+
.from("user_token_ledger")
|
|
207
|
+
.insert({
|
|
208
|
+
owner_id: userId,
|
|
209
|
+
type: "use",
|
|
210
|
+
amount: -tokensRemaining,
|
|
211
|
+
model,
|
|
212
|
+
prompt: truncatedPrompt,
|
|
213
|
+
meta,
|
|
214
|
+
})
|
|
215
|
+
.select();
|
|
216
|
+
insertData = res.data;
|
|
217
|
+
if (res.error)
|
|
218
|
+
throw res.error;
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
logger.error("[debitTokensWithPriority] Ledger insert failed (full payload):", err);
|
|
222
|
+
// Retry with minimal payload (avoid meta or potentially invalid fields)
|
|
223
|
+
try {
|
|
224
|
+
const res2 = await supabase
|
|
225
|
+
.from("user_token_ledger")
|
|
226
|
+
.insert({
|
|
227
|
+
owner_id: userId,
|
|
228
|
+
type: "use",
|
|
229
|
+
amount: -tokensRemaining,
|
|
230
|
+
model,
|
|
231
|
+
})
|
|
232
|
+
.select();
|
|
233
|
+
insertData = res2.data;
|
|
234
|
+
if (res2.error)
|
|
235
|
+
throw res2.error;
|
|
236
|
+
logger.debug("[debitTokensWithPriority] Purchased tokens debited with fallback insert:", insertData);
|
|
237
|
+
}
|
|
238
|
+
catch (err2) {
|
|
239
|
+
logger.error("[debitTokensWithPriority] Ledger insert fallback failed:", err2);
|
|
240
|
+
return {
|
|
241
|
+
success: false,
|
|
242
|
+
debitedFromQuota,
|
|
243
|
+
debitedFromPurchased: 0,
|
|
244
|
+
remainingQuota: quotaStatus.remainingQuota - debitedFromQuota,
|
|
245
|
+
remainingPurchased: purchasedBalance,
|
|
246
|
+
error: (err2 && err2.message) ||
|
|
247
|
+
"Erreur lors du débit des tokens achetés (ledger insert)",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
logger.debug(`[debitTokensWithPriority] Purchased tokens debited successfully:`, insertData);
|
|
252
|
+
debitedFromPurchased = tokensRemaining;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
logger.debug(`[debitTokensWithPriority] No purchased tokens to debit`);
|
|
256
|
+
}
|
|
257
|
+
// Step 4: Get final balances
|
|
258
|
+
const finalQuotaStatus = await getUserQuotaStatus(userId);
|
|
259
|
+
const { data: finalBalanceData } = await supabase
|
|
260
|
+
.from("user_token_balance_v")
|
|
261
|
+
.select("balance")
|
|
262
|
+
.eq("owner_id", userId)
|
|
263
|
+
.single();
|
|
264
|
+
const result = {
|
|
265
|
+
success: true,
|
|
266
|
+
debitedFromQuota,
|
|
267
|
+
debitedFromPurchased,
|
|
268
|
+
remainingQuota: finalQuotaStatus.remainingQuota,
|
|
269
|
+
remainingPurchased: finalBalanceData?.balance || 0,
|
|
270
|
+
};
|
|
271
|
+
logger.debug(`[debitTokensWithPriority] SUCCESS:`, result);
|
|
272
|
+
return result;
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
logger.error("[debitTokensWithPriority] FATAL ERROR:", error);
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
debitedFromQuota: 0,
|
|
279
|
+
debitedFromPurchased: 0,
|
|
280
|
+
remainingQuota: 0,
|
|
281
|
+
remainingPurchased: 0,
|
|
282
|
+
error: error.message || "Erreur lors du débit des tokens",
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Update or create user quota (for subscription updates/renewals)
|
|
288
|
+
*/
|
|
289
|
+
export async function updateUserQuota(request) {
|
|
290
|
+
const supabase = await getSupabaseServiceClient();
|
|
291
|
+
try {
|
|
292
|
+
const defaultTokens = PLAN_DEFAULTS[request.plan] || 0;
|
|
293
|
+
const effectiveTokens = request.custom_included_tokens ?? defaultTokens;
|
|
294
|
+
const quotaData = {
|
|
295
|
+
owner_id: request.owner_id,
|
|
296
|
+
plan: request.plan,
|
|
297
|
+
default_included_tokens: defaultTokens,
|
|
298
|
+
custom_included_tokens: request.custom_included_tokens ?? null,
|
|
299
|
+
effective_included_tokens: effectiveTokens,
|
|
300
|
+
period_start: request.period_start,
|
|
301
|
+
period_end: request.period_end,
|
|
302
|
+
subscription_id: request.subscription_id || null,
|
|
303
|
+
source: "stripe",
|
|
304
|
+
};
|
|
305
|
+
// Reset usage if requested
|
|
306
|
+
if (request.reset_usage) {
|
|
307
|
+
quotaData.used_included_tokens = 0;
|
|
308
|
+
}
|
|
309
|
+
const { error } = await supabase
|
|
310
|
+
.from("user_token_quota_monthly")
|
|
311
|
+
.upsert(quotaData, {
|
|
312
|
+
onConflict: "owner_id",
|
|
313
|
+
});
|
|
314
|
+
if (error) {
|
|
315
|
+
logger.error("[updateUserQuota] Error:", error);
|
|
316
|
+
return { success: false, error: error.message };
|
|
317
|
+
}
|
|
318
|
+
return { success: true };
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
logger.error("[updateUserQuota] Error:", error);
|
|
322
|
+
return { success: false, error: error.message };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Handle subscription update from billing-pro webhook
|
|
327
|
+
* This function is called by billing-pro when subscription events occur
|
|
328
|
+
*
|
|
329
|
+
* @param params Subscription parameters from Stripe
|
|
330
|
+
* @returns Success status
|
|
331
|
+
*/
|
|
332
|
+
export async function handleSubscriptionQuotaUpdate(params) {
|
|
333
|
+
const { owner_id, plan_slug, subscription_id, current_period_start, current_period_end, } = params;
|
|
334
|
+
logger.debug(`[handleSubscriptionQuotaUpdate] Processing for user ${owner_id}, plan ${plan_slug}`);
|
|
335
|
+
try {
|
|
336
|
+
// Map plan slug to plan type
|
|
337
|
+
// Note: Starter plan receives same 100K token quota as Pro plan
|
|
338
|
+
const planMapping = {
|
|
339
|
+
pro: "pro",
|
|
340
|
+
premium: "premium",
|
|
341
|
+
enterprise: "enterprise",
|
|
342
|
+
starter: "pro", // Starter plans receive Pro quota (100K tokens/month)
|
|
343
|
+
free: "free",
|
|
344
|
+
};
|
|
345
|
+
const planType = planMapping[plan_slug.toLowerCase()] || "free";
|
|
346
|
+
// Check if there's an existing custom quota
|
|
347
|
+
const supabase = await getSupabaseServiceClient();
|
|
348
|
+
const { data: existingQuota } = await supabase
|
|
349
|
+
.from("user_token_quota_monthly")
|
|
350
|
+
.select("custom_included_tokens")
|
|
351
|
+
.eq("owner_id", owner_id)
|
|
352
|
+
.single();
|
|
353
|
+
// Update quota with period reset
|
|
354
|
+
const result = await updateUserQuota({
|
|
355
|
+
owner_id,
|
|
356
|
+
plan: planType,
|
|
357
|
+
custom_included_tokens: existingQuota?.custom_included_tokens ?? null,
|
|
358
|
+
period_start: current_period_start,
|
|
359
|
+
period_end: current_period_end,
|
|
360
|
+
subscription_id,
|
|
361
|
+
reset_usage: true, // Reset used tokens on renewal
|
|
362
|
+
});
|
|
363
|
+
if (!result.success) {
|
|
364
|
+
logger.error(`[handleSubscriptionQuotaUpdate] Failed: ${result.error}`);
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
logger.debug(`[handleSubscriptionQuotaUpdate] Success for user ${owner_id}`);
|
|
368
|
+
return { success: true };
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
logger.error("[handleSubscriptionQuotaUpdate] Error:", error);
|
|
372
|
+
return { success: false, error: error.message };
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Update custom quota for a user (admin override)
|
|
377
|
+
*/
|
|
378
|
+
export async function updateCustomQuota(userId, customTokens) {
|
|
379
|
+
const supabase = await getSupabaseServiceClient();
|
|
380
|
+
try {
|
|
381
|
+
// Get current quota
|
|
382
|
+
const { data: currentQuota, error: fetchError } = await supabase
|
|
383
|
+
.from("user_token_quota_monthly")
|
|
384
|
+
.select("*")
|
|
385
|
+
.eq("owner_id", userId)
|
|
386
|
+
.single();
|
|
387
|
+
if (fetchError && fetchError.code !== "PGRST116") {
|
|
388
|
+
return { success: false, error: fetchError.message };
|
|
389
|
+
}
|
|
390
|
+
if (!currentQuota) {
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
error: "Aucun quota trouvé pour cet utilisateur",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const defaultTokens = PLAN_DEFAULTS[currentQuota.plan] || 0;
|
|
397
|
+
const effectiveTokens = customTokens ?? defaultTokens;
|
|
398
|
+
const { error } = await supabase
|
|
399
|
+
.from("user_token_quota_monthly")
|
|
400
|
+
.update({
|
|
401
|
+
custom_included_tokens: customTokens,
|
|
402
|
+
effective_included_tokens: effectiveTokens,
|
|
403
|
+
})
|
|
404
|
+
.eq("owner_id", userId);
|
|
405
|
+
if (error) {
|
|
406
|
+
return { success: false, error: error.message };
|
|
407
|
+
}
|
|
408
|
+
return { success: true };
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
logger.error("[updateCustomQuota] Error:", error);
|
|
412
|
+
return { success: false, error: error.message };
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Get quota usage history for a user
|
|
417
|
+
*/
|
|
418
|
+
export async function getQuotaUsageHistory(userId, limit = 50, offset = 0) {
|
|
419
|
+
const supabase = await getSupabaseServiceClient();
|
|
420
|
+
try {
|
|
421
|
+
const { data, error } = await supabase
|
|
422
|
+
.from("user_token_quota_ledger")
|
|
423
|
+
.select("*")
|
|
424
|
+
.eq("owner_id", userId)
|
|
425
|
+
.order("created_at", { ascending: false })
|
|
426
|
+
.range(offset, offset + limit - 1);
|
|
427
|
+
if (error)
|
|
428
|
+
throw error;
|
|
429
|
+
return data || [];
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
logger.error("[getQuotaUsageHistory] Error:", error);
|
|
433
|
+
return [];
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Refund/credit tokens back to user in case of error after debit
|
|
438
|
+
* This ensures users don't lose tokens if the operation fails after AI generation
|
|
439
|
+
*
|
|
440
|
+
* @param userId User ID
|
|
441
|
+
* @param tokensToRefund Number of tokens to credit back
|
|
442
|
+
* @param model Model that was used (for logging)
|
|
443
|
+
* @param reason Reason for the refund (for logging)
|
|
444
|
+
* @returns Success status with refund details
|
|
445
|
+
*/
|
|
446
|
+
export async function refundTokens(userId, tokensToRefund, model, reason) {
|
|
447
|
+
logger.debug(`[refundTokens] START for user ${userId}: tokensToRefund=${tokensToRefund}, model=${model}, reason=${reason}`);
|
|
448
|
+
if (tokensToRefund <= 0) {
|
|
449
|
+
return {
|
|
450
|
+
success: true,
|
|
451
|
+
refundedToQuota: 0,
|
|
452
|
+
refundedToPurchased: 0,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
const supabase = await getSupabaseServiceClient();
|
|
456
|
+
try {
|
|
457
|
+
let refundedToQuota = 0;
|
|
458
|
+
let refundedToPurchased = 0;
|
|
459
|
+
let remainingToRefund = tokensToRefund;
|
|
460
|
+
// 1. Check if user has active quota that was debited
|
|
461
|
+
const quotaStatus = await getUserQuotaStatus(userId);
|
|
462
|
+
if (quotaStatus.hasQuota && quotaStatus.isActive) {
|
|
463
|
+
const { data: quotaData } = await supabase
|
|
464
|
+
.from("user_token_quota_monthly")
|
|
465
|
+
.select("used_included_tokens, effective_included_tokens")
|
|
466
|
+
.eq("owner_id", userId)
|
|
467
|
+
.single();
|
|
468
|
+
if (quotaData && quotaData.used_included_tokens > 0) {
|
|
469
|
+
// Calculate how much to refund to quota (cannot exceed what was used)
|
|
470
|
+
const maxQuotaRefund = Math.min(remainingToRefund, quotaData.used_included_tokens);
|
|
471
|
+
if (maxQuotaRefund > 0) {
|
|
472
|
+
const newUsed = quotaData.used_included_tokens - maxQuotaRefund;
|
|
473
|
+
const { error: quotaError } = await supabase
|
|
474
|
+
.from("user_token_quota_monthly")
|
|
475
|
+
.update({ used_included_tokens: newUsed })
|
|
476
|
+
.eq("owner_id", userId);
|
|
477
|
+
if (quotaError) {
|
|
478
|
+
logger.error("[refundTokens] Quota refund error:", quotaError);
|
|
479
|
+
}
|
|
480
|
+
else {
|
|
481
|
+
refundedToQuota = maxQuotaRefund;
|
|
482
|
+
remainingToRefund -= maxQuotaRefund;
|
|
483
|
+
logger.debug(`[refundTokens] Refunded ${maxQuotaRefund} tokens to quota`);
|
|
484
|
+
// Log quota refund
|
|
485
|
+
await supabase.from("user_token_quota_ledger").insert({
|
|
486
|
+
owner_id: userId,
|
|
487
|
+
tokens_used: -maxQuotaRefund, // Negative = refund
|
|
488
|
+
model,
|
|
489
|
+
prompt: `REFUND: ${reason.substring(0, 32)}`,
|
|
490
|
+
period_start: quotaStatus.periodStart,
|
|
491
|
+
period_end: quotaStatus.periodEnd,
|
|
492
|
+
meta: { refund: true, reason },
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
// 2. Refund remaining to purchased tokens
|
|
499
|
+
if (remainingToRefund > 0) {
|
|
500
|
+
const { error: ledgerError } = await supabase
|
|
501
|
+
.from("user_token_ledger")
|
|
502
|
+
.insert({
|
|
503
|
+
owner_id: userId,
|
|
504
|
+
type: "refund",
|
|
505
|
+
amount: remainingToRefund, // Positive = credit
|
|
506
|
+
model,
|
|
507
|
+
prompt: `REFUND: ${reason.substring(0, 32)}`,
|
|
508
|
+
meta: { refund: true, reason },
|
|
509
|
+
});
|
|
510
|
+
if (ledgerError) {
|
|
511
|
+
logger.error("[refundTokens] Ledger refund error:", ledgerError);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
refundedToPurchased = remainingToRefund;
|
|
515
|
+
logger.debug(`[refundTokens] Refunded ${remainingToRefund} tokens to purchased balance`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
logger.debug("[refundTokens] SUCCESS:", {
|
|
519
|
+
refundedToQuota,
|
|
520
|
+
refundedToPurchased,
|
|
521
|
+
total: refundedToQuota + refundedToPurchased,
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
success: true,
|
|
525
|
+
refundedToQuota,
|
|
526
|
+
refundedToPurchased,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
logger.error("[refundTokens] Error:", error);
|
|
531
|
+
return {
|
|
532
|
+
success: false,
|
|
533
|
+
error: error.message,
|
|
534
|
+
refundedToQuota: 0,
|
|
535
|
+
refundedToPurchased: 0,
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Repair Utilities
|
|
3
|
+
* For fixing wallets that were not initialized correctly (legacy credits, manual edits, etc.)
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Recalculate wallet budget from ledger history
|
|
7
|
+
* Useful for users who have tokens but wallet = 0 USD
|
|
8
|
+
*/
|
|
9
|
+
export declare function repairUserWallet(userId: string, tokenBalance?: number): Promise<{
|
|
10
|
+
success: boolean;
|
|
11
|
+
error?: string;
|
|
12
|
+
before?: {
|
|
13
|
+
balance: number;
|
|
14
|
+
providerBudget: number;
|
|
15
|
+
sellValue: number;
|
|
16
|
+
};
|
|
17
|
+
after?: {
|
|
18
|
+
balance: number;
|
|
19
|
+
providerBudget: number;
|
|
20
|
+
sellValue: number;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Repair all wallets in the system
|
|
25
|
+
*/
|
|
26
|
+
export declare function repairAllWallets(): Promise<{
|
|
27
|
+
success: boolean;
|
|
28
|
+
repaired: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
errors: string[];
|
|
31
|
+
}>;
|
|
32
|
+
//# sourceMappingURL=wallet-repair.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet-repair.d.ts","sourceRoot":"","sources":["../../src/server/wallet-repair.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE,CAAC,CAgKD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CA4DD"}
|