@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.
Files changed (365) hide show
  1. package/README.md +52 -1
  2. package/dist/ai.build.config.d.ts.map +1 -1
  3. package/dist/ai.build.config.js +508 -9
  4. package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
  5. package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
  6. package/dist/api/admin/ai-provider-models/[id].js +58 -0
  7. package/dist/api/admin/ai-provider-models.d.ts +20 -0
  8. package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
  9. package/dist/api/admin/ai-provider-models.js +26 -0
  10. package/dist/api/admin/ai-providers/[key].d.ts +18 -0
  11. package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
  12. package/dist/api/admin/ai-providers/[key].js +55 -0
  13. package/dist/api/admin/ai-providers.d.ts +20 -0
  14. package/dist/api/admin/ai-providers.d.ts.map +1 -0
  15. package/dist/api/admin/ai-providers.js +26 -0
  16. package/dist/api/admin/billing-analytics.d.ts +43 -0
  17. package/dist/api/admin/billing-analytics.d.ts.map +1 -0
  18. package/dist/api/admin/billing-analytics.js +144 -0
  19. package/dist/api/admin/global-ai-settings.d.ts +14 -0
  20. package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
  21. package/dist/api/admin/global-ai-settings.js +63 -0
  22. package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
  23. package/dist/api/admin/token-packs/[id].js +3 -2
  24. package/dist/api/admin/token-packs.d.ts.map +1 -1
  25. package/dist/api/admin/token-packs.js +3 -2
  26. package/dist/api/admin/user-monthly-details.d.ts +49 -0
  27. package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
  28. package/dist/api/admin/user-monthly-details.js +140 -0
  29. package/dist/api/admin/user-quota.d.ts +21 -0
  30. package/dist/api/admin/user-quota.d.ts.map +1 -0
  31. package/dist/api/admin/user-quota.js +59 -0
  32. package/dist/api/admin/user-token/[id].d.ts.map +1 -1
  33. package/dist/api/admin/user-token/[id].js +2 -1
  34. package/dist/api/admin/user-token.d.ts +5 -2
  35. package/dist/api/admin/user-token.d.ts.map +1 -1
  36. package/dist/api/admin/user-token.js +91 -17
  37. package/dist/api/admin/user-usage-by-model.d.ts +22 -0
  38. package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
  39. package/dist/api/admin/user-usage-by-model.js +78 -0
  40. package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
  41. package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
  42. package/dist/api/admin/user-wallet-analytics.js +67 -0
  43. package/dist/api/admin/wallet-repair/route.d.ts +30 -0
  44. package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
  45. package/dist/api/admin/wallet-repair/route.js +63 -0
  46. package/dist/api/auth/ai-model-settings.d.ts +21 -0
  47. package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
  48. package/dist/api/auth/ai-model-settings.js +86 -0
  49. package/dist/api/auth/ai-settings.d.ts +17 -0
  50. package/dist/api/auth/ai-settings.d.ts.map +1 -0
  51. package/dist/api/auth/ai-settings.js +87 -0
  52. package/dist/api/auth/api-keys/[id].d.ts +17 -0
  53. package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
  54. package/dist/api/auth/api-keys/[id].js +66 -0
  55. package/dist/api/auth/api-keys.d.ts +19 -0
  56. package/dist/api/auth/api-keys.d.ts.map +1 -0
  57. package/dist/api/auth/api-keys.js +94 -0
  58. package/dist/api/auth/create-checkout.d.ts +1 -1
  59. package/dist/api/auth/create-checkout.d.ts.map +1 -1
  60. package/dist/api/auth/create-checkout.js +8 -6
  61. package/dist/api/auth/generate-image.d.ts +2 -2
  62. package/dist/api/auth/generate-image.d.ts.map +1 -1
  63. package/dist/api/auth/generate-image.js +404 -104
  64. package/dist/api/auth/generate-text.d.ts +3 -2
  65. package/dist/api/auth/generate-text.d.ts.map +1 -1
  66. package/dist/api/auth/generate-text.js +130 -58
  67. package/dist/api/auth/process-ocr.d.ts +9 -0
  68. package/dist/api/auth/process-ocr.d.ts.map +1 -0
  69. package/dist/api/auth/process-ocr.js +43 -0
  70. package/dist/api/auth/prompts/stats.d.ts +14 -0
  71. package/dist/api/auth/prompts/stats.d.ts.map +1 -0
  72. package/dist/api/auth/prompts/stats.js +46 -0
  73. package/dist/api/auth/prompts.d.ts +26 -0
  74. package/dist/api/auth/prompts.d.ts.map +1 -0
  75. package/dist/api/auth/prompts.js +175 -0
  76. package/dist/api/auth/token-balance.d.ts +26 -0
  77. package/dist/api/auth/token-balance.d.ts.map +1 -0
  78. package/dist/api/auth/token-balance.js +47 -0
  79. package/dist/api/auth/token-checkout.d.ts.map +1 -1
  80. package/dist/api/auth/token-checkout.js +22 -4
  81. package/dist/api/auth/token-packs.d.ts.map +1 -1
  82. package/dist/api/auth/token-packs.js +2 -1
  83. package/dist/api/auth/usage-by-model.d.ts +25 -0
  84. package/dist/api/auth/usage-by-model.d.ts.map +1 -0
  85. package/dist/api/auth/usage-by-model.js +95 -0
  86. package/dist/api/auth/usage.d.ts +26 -0
  87. package/dist/api/auth/usage.d.ts.map +1 -0
  88. package/dist/api/auth/usage.js +127 -0
  89. package/dist/api/auth/user-tokens.d.ts.map +1 -1
  90. package/dist/api/auth/user-tokens.js +36 -2
  91. package/dist/api/auth/wallet/route.d.ts +17 -0
  92. package/dist/api/auth/wallet/route.d.ts.map +1 -0
  93. package/dist/api/auth/wallet/route.js +68 -0
  94. package/dist/api/auth/wallet.d.ts +16 -0
  95. package/dist/api/auth/wallet.d.ts.map +1 -0
  96. package/dist/api/auth/wallet.js +71 -0
  97. package/dist/api/public/gateway-models.d.ts +25 -0
  98. package/dist/api/public/gateway-models.d.ts.map +1 -0
  99. package/dist/api/public/gateway-models.js +49 -0
  100. package/dist/api/public/pricing-summary.d.ts +46 -0
  101. package/dist/api/public/pricing-summary.d.ts.map +1 -0
  102. package/dist/api/public/pricing-summary.js +70 -0
  103. package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
  104. package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
  105. package/dist/api/public/prompts/[id]/stats.js +37 -0
  106. package/dist/api/public/prompts/[id]/view.d.ts +15 -0
  107. package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
  108. package/dist/api/public/prompts/[id]/view.js +41 -0
  109. package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
  110. package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
  111. package/dist/api/public/prompts/slug/[slug].js +40 -0
  112. package/dist/api/public/prompts/user/[username].d.ts +17 -0
  113. package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
  114. package/dist/api/public/prompts/user/[username].js +51 -0
  115. package/dist/api/public/prompts.d.ts +15 -0
  116. package/dist/api/public/prompts.d.ts.map +1 -0
  117. package/dist/api/public/prompts.js +45 -0
  118. package/dist/api/public/token-packs.d.ts +11 -0
  119. package/dist/api/public/token-packs.d.ts.map +1 -0
  120. package/dist/api/public/token-packs.js +25 -0
  121. package/dist/api/public/token-pricing.d.ts +44 -0
  122. package/dist/api/public/token-pricing.d.ts.map +1 -0
  123. package/dist/api/public/token-pricing.js +168 -0
  124. package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
  125. package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
  126. package/dist/api/public/v1/_lib/artifacts.js +217 -0
  127. package/dist/api/public/v1/_lib/auth.d.ts +43 -0
  128. package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
  129. package/dist/api/public/v1/_lib/auth.js +108 -0
  130. package/dist/api/public/v1/_lib/errors.d.ts +17 -0
  131. package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
  132. package/dist/api/public/v1/_lib/errors.js +16 -0
  133. package/dist/api/public/v1/_lib/log.d.ts +29 -0
  134. package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
  135. package/dist/api/public/v1/_lib/log.js +68 -0
  136. package/dist/api/public/v1/_lib/quota.d.ts +24 -0
  137. package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
  138. package/dist/api/public/v1/_lib/quota.js +118 -0
  139. package/dist/api/public/v1/_lib/router.d.ts +54 -0
  140. package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
  141. package/dist/api/public/v1/_lib/router.js +119 -0
  142. package/dist/api/public/v1/connect.d.ts +20 -0
  143. package/dist/api/public/v1/connect.d.ts.map +1 -0
  144. package/dist/api/public/v1/connect.js +119 -0
  145. package/dist/api/public/v1/doc.d.ts +239 -0
  146. package/dist/api/public/v1/doc.d.ts.map +1 -0
  147. package/dist/api/public/v1/doc.js +253 -0
  148. package/dist/api/public/v1/history/[id].d.ts +92 -0
  149. package/dist/api/public/v1/history/[id].d.ts.map +1 -0
  150. package/dist/api/public/v1/history/[id].js +176 -0
  151. package/dist/api/public/v1/history.d.ts +30 -0
  152. package/dist/api/public/v1/history.d.ts.map +1 -0
  153. package/dist/api/public/v1/history.js +142 -0
  154. package/dist/api/public/v1/image-ai.d.ts +24 -0
  155. package/dist/api/public/v1/image-ai.d.ts.map +1 -0
  156. package/dist/api/public/v1/image-ai.js +233 -0
  157. package/dist/api/public/v1/prompts.d.ts +19 -0
  158. package/dist/api/public/v1/prompts.d.ts.map +1 -0
  159. package/dist/api/public/v1/prompts.js +107 -0
  160. package/dist/api/public/v1/provider.d.ts +16 -0
  161. package/dist/api/public/v1/provider.d.ts.map +1 -0
  162. package/dist/api/public/v1/provider.js +130 -0
  163. package/dist/api/public/v1/purchase.d.ts +11 -0
  164. package/dist/api/public/v1/purchase.d.ts.map +1 -0
  165. package/dist/api/public/v1/purchase.js +18 -0
  166. package/dist/api/public/v1/status.d.ts +35 -0
  167. package/dist/api/public/v1/status.d.ts.map +1 -0
  168. package/dist/api/public/v1/status.js +163 -0
  169. package/dist/api/public/v1/text-ai.d.ts +26 -0
  170. package/dist/api/public/v1/text-ai.d.ts.map +1 -0
  171. package/dist/api/public/v1/text-ai.js +239 -0
  172. package/dist/api/public/webhook.d.ts.map +1 -1
  173. package/dist/api/public/webhook.js +50 -39
  174. package/dist/api/track-usage.d.ts +12 -0
  175. package/dist/api/track-usage.d.ts.map +1 -0
  176. package/dist/api/track-usage.js +37 -0
  177. package/dist/components/Doc.d.ts.map +1 -1
  178. package/dist/components/Doc.js +1 -1
  179. package/dist/components/DocUsageCustom.js +6 -6
  180. package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
  181. package/dist/components/admin/UserTokenTab.js +170 -23
  182. package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
  183. package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
  184. package/dist/components/auth/AuthDashboardAi.js +53 -0
  185. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
  186. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
  187. package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
  188. package/dist/index.d.ts +15 -1
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +18 -1
  191. package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
  192. package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
  193. package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
  194. package/dist/server/__tests__/billing.test.d.ts +5 -0
  195. package/dist/server/__tests__/billing.test.d.ts.map +1 -0
  196. package/dist/server/__tests__/billing.test.js +223 -0
  197. package/dist/server/ai-client.d.ts +59 -0
  198. package/dist/server/ai-client.d.ts.map +1 -0
  199. package/dist/server/ai-client.js +111 -0
  200. package/dist/server/ai-generation-service.d.ts +66 -0
  201. package/dist/server/ai-generation-service.d.ts.map +1 -0
  202. package/dist/server/ai-generation-service.js +274 -0
  203. package/dist/server/billing.d.ts +200 -0
  204. package/dist/server/billing.d.ts.map +1 -0
  205. package/dist/server/billing.js +488 -0
  206. package/dist/server/gateway-service.d.ts +13 -0
  207. package/dist/server/gateway-service.d.ts.map +1 -0
  208. package/dist/server/gateway-service.js +161 -0
  209. package/dist/server/global-settings.d.ts +16 -0
  210. package/dist/server/global-settings.d.ts.map +1 -0
  211. package/dist/server/global-settings.js +42 -0
  212. package/dist/server/model-filter.d.ts +25 -0
  213. package/dist/server/model-filter.d.ts.map +1 -0
  214. package/dist/server/model-filter.js +240 -0
  215. package/dist/server/ocr.d.ts +39 -0
  216. package/dist/server/ocr.d.ts.map +1 -0
  217. package/dist/server/ocr.js +280 -0
  218. package/dist/server/openai-client.d.ts +19 -0
  219. package/dist/server/openai-client.d.ts.map +1 -0
  220. package/dist/server/openai-client.js +26 -0
  221. package/dist/server/pricing-config.d.ts +18 -0
  222. package/dist/server/pricing-config.d.ts.map +1 -0
  223. package/dist/server/pricing-config.js +94 -0
  224. package/dist/server/pricing-validator.d.ts +41 -0
  225. package/dist/server/pricing-validator.d.ts.map +1 -0
  226. package/dist/server/pricing-validator.js +113 -0
  227. package/dist/server/pricing.d.ts +121 -0
  228. package/dist/server/pricing.d.ts.map +1 -0
  229. package/dist/server/pricing.js +225 -0
  230. package/dist/server/quota.d.ts +66 -0
  231. package/dist/server/quota.d.ts.map +1 -0
  232. package/dist/server/quota.js +538 -0
  233. package/dist/server/wallet-repair.d.ts +32 -0
  234. package/dist/server/wallet-repair.d.ts.map +1 -0
  235. package/dist/server/wallet-repair.js +189 -0
  236. package/dist/server.d.ts +13 -1
  237. package/dist/server.d.ts.map +1 -1
  238. package/dist/server.js +87 -16
  239. package/dist/sitemap/handlers/prompts.d.ts +6 -0
  240. package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
  241. package/dist/sitemap/handlers/prompts.js +72 -0
  242. package/dist/sitemap/handlers/users.d.ts +6 -0
  243. package/dist/sitemap/handlers/users.d.ts.map +1 -0
  244. package/dist/sitemap/handlers/users.js +80 -0
  245. package/dist/sitemap/manifest.d.ts +8 -0
  246. package/dist/sitemap/manifest.d.ts.map +1 -0
  247. package/dist/sitemap/manifest.js +27 -0
  248. package/dist/types/gateway.d.ts +40 -0
  249. package/dist/types/gateway.d.ts.map +1 -0
  250. package/dist/types/gateway.js +4 -0
  251. package/dist/types/quota.d.ts +74 -0
  252. package/dist/types/quota.d.ts.map +1 -0
  253. package/dist/types/quota.js +11 -0
  254. package/dist/utils/date.d.ts +7 -0
  255. package/dist/utils/date.d.ts.map +1 -0
  256. package/dist/utils/date.js +17 -0
  257. package/dist/web/admin/AdminTokenPacksPage.js +1 -1
  258. package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
  259. package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
  260. package/dist/web/admin/BillingAnalyticsPage.js +141 -0
  261. package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
  262. package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
  263. package/dist/web/admin/GlobalAISettingsPage.js +93 -0
  264. package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
  265. package/dist/web/admin/UserTokenPage.js +20 -7
  266. package/dist/web/auth/AISettingsPage.d.ts +2 -0
  267. package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
  268. package/dist/web/auth/AISettingsPage.js +258 -0
  269. package/dist/web/auth/APIKeysPage.d.ts +2 -0
  270. package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
  271. package/dist/web/auth/APIKeysPage.js +154 -0
  272. package/dist/web/auth/HistoryPage.d.ts +2 -0
  273. package/dist/web/auth/HistoryPage.d.ts.map +1 -0
  274. package/dist/web/auth/HistoryPage.js +279 -0
  275. package/dist/web/auth/PromptsPage.d.ts +5 -0
  276. package/dist/web/auth/PromptsPage.d.ts.map +1 -0
  277. package/dist/web/auth/PromptsPage.js +137 -0
  278. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  279. package/dist/web/auth/TokenPage.js +88 -31
  280. package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
  281. package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
  282. package/dist/web/auth/UsageAndTokensPage.js +157 -0
  283. package/dist/web/auth/UsagePage.d.ts +2 -0
  284. package/dist/web/auth/UsagePage.d.ts.map +1 -0
  285. package/dist/web/auth/UsagePage.js +62 -0
  286. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
  287. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
  288. package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
  289. package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
  290. package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
  291. package/dist/web/auth/components/ModelUsageTable.js +37 -0
  292. package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
  293. package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
  294. package/dist/web/auth/components/PurchaseButton.js +13 -0
  295. package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
  296. package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
  297. package/dist/web/auth/components/TokenHistoryCard.js +76 -0
  298. package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
  299. package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
  300. package/dist/web/auth/components/TokenKpiGrid.js +38 -0
  301. package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
  302. package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
  303. package/dist/web/auth/components/UsageByDayChart.js +32 -0
  304. package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
  305. package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
  306. package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
  307. package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
  308. package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
  309. package/dist/web/auth/components/WalletStatusCard.js +50 -0
  310. package/dist/web/components/ImageGenerative.d.ts +3 -1
  311. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  312. package/dist/web/components/ImageGenerative.js +139 -52
  313. package/dist/web/components/TextareaGenerative.d.ts +3 -1
  314. package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
  315. package/dist/web/components/TextareaGenerative.js +10 -5
  316. package/dist/web/public/PromptDetailPage.d.ts +25 -0
  317. package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
  318. package/dist/web/public/PromptDetailPage.js +71 -0
  319. package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
  320. package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
  321. package/dist/web/public/PromptDetailPageServer.js +68 -0
  322. package/dist/web/public/PublicPromptsPage.d.ts +5 -0
  323. package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
  324. package/dist/web/public/PublicPromptsPage.js +110 -0
  325. package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
  326. package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
  327. package/dist/web/public/PurchaseTokensPage.js +98 -0
  328. package/dist/web/public/UserAvatar.d.ts +13 -0
  329. package/dist/web/public/UserAvatar.d.ts.map +1 -0
  330. package/dist/web/public/UserAvatar.js +13 -0
  331. package/dist/web/public/UserDetailPageServer.d.ts +15 -0
  332. package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
  333. package/dist/web/public/UserDetailPageServer.js +31 -0
  334. package/dist/web/public/UserPromptsPage.d.ts +9 -0
  335. package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
  336. package/dist/web/public/UserPromptsPage.js +112 -0
  337. package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
  338. package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
  339. package/dist/web/public/UserPromptsPageServer.js +31 -0
  340. package/package.json +18 -9
  341. package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
  342. package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
  343. package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
  344. package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
  345. package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
  346. package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
  347. package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
  348. package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
  349. package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
  350. package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
  351. package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
  352. package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
  353. package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
  354. package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
  355. package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
  356. package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
  357. package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
  358. package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
  359. package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
  360. package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
  361. package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
  362. package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
  363. package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
  364. package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
  365. package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
@@ -0,0 +1,151 @@
1
+ -- ============================================================================
2
+ -- AI User Prompts System
3
+ -- ============================================================================
4
+
5
+ -- Table: user_prompt
6
+ -- Store user's saved prompts (private or public)
7
+ CREATE TABLE IF NOT EXISTS user_prompt (
8
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
9
+ owner_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
10
+ title TEXT NOT NULL,
11
+ content TEXT NOT NULL,
12
+ type TEXT NOT NULL CHECK (type IN ('text', 'image')),
13
+ is_public BOOLEAN NOT NULL DEFAULT false,
14
+ favorite BOOLEAN NOT NULL DEFAULT false,
15
+ tags TEXT[] DEFAULT '{}',
16
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
17
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
18
+ );
19
+
20
+ -- Indexes
21
+ CREATE INDEX idx_user_prompt_owner ON user_prompt(owner_id);
22
+ CREATE INDEX idx_user_prompt_type ON user_prompt(type);
23
+ CREATE INDEX idx_user_prompt_public ON user_prompt(is_public) WHERE is_public = true;
24
+ CREATE INDEX idx_user_prompt_favorite ON user_prompt(owner_id, favorite) WHERE favorite = true;
25
+ CREATE INDEX idx_user_prompt_tags ON user_prompt USING GIN(tags);
26
+
27
+ -- RLS
28
+ ALTER TABLE user_prompt ENABLE ROW LEVEL SECURITY;
29
+
30
+ -- Policy: Superadmin can do everything
31
+ CREATE POLICY "superadmin_all_user_prompt" ON user_prompt
32
+ FOR ALL
33
+ USING (is_superadmin(auth.uid()));
34
+
35
+ -- Policy: Users can view their own prompts
36
+ CREATE POLICY "user_select_own_prompts" ON user_prompt
37
+ FOR SELECT
38
+ USING (owner_id = auth.uid());
39
+
40
+ -- Policy: Users can view public prompts
41
+ CREATE POLICY "user_select_public_prompts" ON user_prompt
42
+ FOR SELECT
43
+ USING (is_public = true);
44
+
45
+ -- Policy: Users can insert their own prompts
46
+ CREATE POLICY "user_insert_own_prompts" ON user_prompt
47
+ FOR INSERT
48
+ WITH CHECK (owner_id = auth.uid());
49
+
50
+ -- Policy: Users can update their own prompts
51
+ CREATE POLICY "user_update_own_prompts" ON user_prompt
52
+ FOR UPDATE
53
+ USING (owner_id = auth.uid())
54
+ WITH CHECK (owner_id = auth.uid());
55
+
56
+ -- Policy: Users can delete their own prompts
57
+ CREATE POLICY "user_delete_own_prompts" ON user_prompt
58
+ FOR DELETE
59
+ USING (owner_id = auth.uid());
60
+
61
+ -- Table: prompt_stats
62
+ -- Statistics for public prompts
63
+ CREATE TABLE IF NOT EXISTS prompt_stats (
64
+ prompt_id UUID PRIMARY KEY REFERENCES user_prompt(id) ON DELETE CASCADE,
65
+ views INTEGER NOT NULL DEFAULT 0,
66
+ used_count INTEGER NOT NULL DEFAULT 0,
67
+ picked_count INTEGER NOT NULL DEFAULT 0,
68
+ last_used_at TIMESTAMPTZ,
69
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
70
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
71
+ );
72
+
73
+ -- RLS for prompt_stats (read-only for everyone, write for superadmin)
74
+ ALTER TABLE prompt_stats ENABLE ROW LEVEL SECURITY;
75
+
76
+ CREATE POLICY "anyone_select_prompt_stats" ON prompt_stats
77
+ FOR SELECT
78
+ USING (true);
79
+
80
+ CREATE POLICY "superadmin_all_prompt_stats" ON prompt_stats
81
+ FOR ALL
82
+ USING (is_superadmin(auth.uid()));
83
+
84
+ -- View: public_prompts_with_stats
85
+ -- Public prompts with their stats
86
+ CREATE OR REPLACE VIEW public_prompts_with_stats AS
87
+ SELECT
88
+ p.id,
89
+ p.owner_id,
90
+ p.title,
91
+ p.content,
92
+ p.type,
93
+ p.tags,
94
+ p.created_at,
95
+ COALESCE(s.views, 0) as views,
96
+ COALESCE(s.used_count, 0) as used_count,
97
+ COALESCE(s.picked_count, 0) as picked_count,
98
+ s.last_used_at
99
+ FROM user_prompt p
100
+ LEFT JOIN prompt_stats s ON s.prompt_id = p.id
101
+ WHERE p.is_public = true
102
+ ORDER BY s.picked_count DESC NULLS LAST, p.created_at DESC;
103
+
104
+ -- Grant access to the view
105
+ GRANT SELECT ON public_prompts_with_stats TO authenticated, anon;
106
+
107
+ -- Function: increment_prompt_stat
108
+ -- Increment a specific stat counter
109
+ CREATE OR REPLACE FUNCTION increment_prompt_stat(
110
+ p_prompt_id UUID,
111
+ p_stat_type TEXT
112
+ )
113
+ RETURNS void
114
+ LANGUAGE plpgsql
115
+ SECURITY DEFINER
116
+ AS $$
117
+ BEGIN
118
+ -- Insert or update stats
119
+ INSERT INTO prompt_stats (prompt_id, views, used_count, picked_count, last_used_at)
120
+ VALUES (
121
+ p_prompt_id,
122
+ CASE WHEN p_stat_type = 'view' THEN 1 ELSE 0 END,
123
+ CASE WHEN p_stat_type = 'used' THEN 1 ELSE 0 END,
124
+ CASE WHEN p_stat_type = 'picked' THEN 1 ELSE 0 END,
125
+ CASE WHEN p_stat_type IN ('used', 'picked') THEN now() ELSE NULL END
126
+ )
127
+ ON CONFLICT (prompt_id) DO UPDATE SET
128
+ views = prompt_stats.views + CASE WHEN p_stat_type = 'view' THEN 1 ELSE 0 END,
129
+ used_count = prompt_stats.used_count + CASE WHEN p_stat_type = 'used' THEN 1 ELSE 0 END,
130
+ picked_count = prompt_stats.picked_count + CASE WHEN p_stat_type = 'picked' THEN 1 ELSE 0 END,
131
+ last_used_at = CASE WHEN p_stat_type IN ('used', 'picked') THEN now() ELSE prompt_stats.last_used_at END,
132
+ updated_at = now();
133
+ END;
134
+ $$;
135
+
136
+ -- Grant execute to authenticated and anon users
137
+ GRANT EXECUTE ON FUNCTION increment_prompt_stat TO authenticated, anon;
138
+
139
+ -- Trigger: update_user_prompt_updated_at
140
+ CREATE OR REPLACE FUNCTION update_user_prompt_updated_at()
141
+ RETURNS TRIGGER AS $$
142
+ BEGIN
143
+ NEW.updated_at = now();
144
+ RETURN NEW;
145
+ END;
146
+ $$ LANGUAGE plpgsql;
147
+
148
+ CREATE TRIGGER trigger_update_user_prompt_updated_at
149
+ BEFORE UPDATE ON user_prompt
150
+ FOR EACH ROW
151
+ EXECUTE FUNCTION update_user_prompt_updated_at();
@@ -0,0 +1,92 @@
1
+ -- ============================================================================
2
+ -- AI Prompts IP Tracking System
3
+ -- Add IP-based tracking to prevent duplicate stat increments
4
+ -- ============================================================================
5
+
6
+ -- Table: prompt_stat_tracking
7
+ -- Track which IPs have already incremented specific stats
8
+ CREATE TABLE IF NOT EXISTS prompt_stat_tracking (
9
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
10
+ prompt_id UUID NOT NULL REFERENCES user_prompt(id) ON DELETE CASCADE,
11
+ ip_address TEXT NOT NULL,
12
+ stat_type TEXT NOT NULL CHECK (stat_type IN ('views', 'picked', 'used')),
13
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
14
+ );
15
+
16
+ -- Indexes
17
+ CREATE INDEX idx_prompt_stat_tracking_lookup ON prompt_stat_tracking(prompt_id, ip_address, stat_type);
18
+ CREATE INDEX idx_prompt_stat_tracking_cleanup ON prompt_stat_tracking(created_at);
19
+
20
+ -- RLS (service role only)
21
+ ALTER TABLE prompt_stat_tracking ENABLE ROW LEVEL SECURITY;
22
+
23
+ CREATE POLICY "service_all_prompt_stat_tracking" ON prompt_stat_tracking
24
+ FOR ALL
25
+ USING (is_superadmin(auth.uid()));
26
+
27
+ -- Function: increment_prompt_stat_with_ip
28
+ -- Increment stat only if IP hasn't already done so (with 24h cooldown)
29
+ CREATE OR REPLACE FUNCTION increment_prompt_stat_with_ip(
30
+ p_prompt_id UUID,
31
+ p_stat_type TEXT,
32
+ p_ip_address TEXT
33
+ )
34
+ RETURNS BOOLEAN
35
+ LANGUAGE plpgsql
36
+ SECURITY DEFINER
37
+ AS $$
38
+ DECLARE
39
+ v_already_tracked BOOLEAN;
40
+ BEGIN
41
+ -- Check if this IP has already incremented this stat in the last 24 hours
42
+ SELECT EXISTS (
43
+ SELECT 1 FROM prompt_stat_tracking
44
+ WHERE prompt_id = p_prompt_id
45
+ AND ip_address = p_ip_address
46
+ AND stat_type = p_stat_type
47
+ AND created_at > now() - INTERVAL '24 hours'
48
+ ) INTO v_already_tracked;
49
+
50
+ -- If already tracked, return false (no increment)
51
+ IF v_already_tracked THEN
52
+ RETURN FALSE;
53
+ END IF;
54
+
55
+ -- Record this tracking
56
+ INSERT INTO prompt_stat_tracking (prompt_id, ip_address, stat_type)
57
+ VALUES (p_prompt_id, p_ip_address, p_stat_type);
58
+
59
+ -- Increment the actual stat
60
+ INSERT INTO prompt_stats (prompt_id, views, used_count, picked_count, last_used_at)
61
+ VALUES (
62
+ p_prompt_id,
63
+ CASE WHEN p_stat_type = 'views' THEN 1 ELSE 0 END,
64
+ CASE WHEN p_stat_type = 'used' THEN 1 ELSE 0 END,
65
+ CASE WHEN p_stat_type = 'picked' THEN 1 ELSE 0 END,
66
+ CASE WHEN p_stat_type IN ('used', 'picked') THEN now() ELSE NULL END
67
+ )
68
+ ON CONFLICT (prompt_id) DO UPDATE SET
69
+ views = prompt_stats.views + CASE WHEN p_stat_type = 'views' THEN 1 ELSE 0 END,
70
+ used_count = prompt_stats.used_count + CASE WHEN p_stat_type = 'used' THEN 1 ELSE 0 END,
71
+ picked_count = prompt_stats.picked_count + CASE WHEN p_stat_type = 'picked' THEN 1 ELSE 0 END,
72
+ last_used_at = CASE WHEN p_stat_type IN ('used', 'picked') THEN now() ELSE prompt_stats.last_used_at END,
73
+ updated_at = now();
74
+
75
+ RETURN TRUE;
76
+ END;
77
+ $$;
78
+
79
+ -- Grant execute to authenticated and anon users
80
+ GRANT EXECUTE ON FUNCTION increment_prompt_stat_with_ip TO authenticated, anon;
81
+
82
+ -- Cleanup function: Remove tracking older than 30 days
83
+ CREATE OR REPLACE FUNCTION cleanup_old_prompt_stat_tracking()
84
+ RETURNS void
85
+ LANGUAGE plpgsql
86
+ SECURITY DEFINER
87
+ AS $$
88
+ BEGIN
89
+ DELETE FROM prompt_stat_tracking
90
+ WHERE created_at < now() - INTERVAL '30 days';
91
+ END;
92
+ $$;
@@ -0,0 +1,64 @@
1
+ -- ============================================================================
2
+ -- Add slug column to user_prompt for SEO-friendly URLs
3
+ -- ============================================================================
4
+
5
+ -- Add slug column
6
+ ALTER TABLE user_prompt ADD COLUMN IF NOT EXISTS slug TEXT;
7
+
8
+ -- Create unique index on slug for public prompts
9
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_user_prompt_slug ON user_prompt(slug) WHERE is_public = true AND slug IS NOT NULL;
10
+
11
+ -- Function to generate slug from title
12
+ CREATE OR REPLACE FUNCTION generate_prompt_slug(p_title TEXT)
13
+ RETURNS TEXT
14
+ LANGUAGE plpgsql
15
+ AS $$
16
+ DECLARE
17
+ v_slug TEXT;
18
+ v_short_id TEXT;
19
+ v_counter INTEGER := 0;
20
+ v_final_slug TEXT;
21
+ BEGIN
22
+ -- Slugify title: lowercase, replace spaces with hyphens, remove special chars
23
+ v_slug := lower(regexp_replace(p_title, '[^a-zA-Z0-9\s-]', '', 'g'));
24
+ v_slug := regexp_replace(v_slug, '\s+', '-', 'g');
25
+ v_slug := regexp_replace(v_slug, '-+', '-', 'g');
26
+ v_slug := trim(both '-' from v_slug);
27
+
28
+ -- Limit to 50 characters
29
+ v_slug := substring(v_slug from 1 for 50);
30
+
31
+ -- Generate short random ID (6 chars)
32
+ v_short_id := substring(md5(random()::text || clock_timestamp()::text) from 1 for 6);
33
+
34
+ -- Combine slug with short ID
35
+ v_final_slug := v_slug || '-' || v_short_id;
36
+
37
+ RETURN v_final_slug;
38
+ END;
39
+ $$;
40
+
41
+ -- Function to auto-generate slug on insert/update if missing
42
+ CREATE OR REPLACE FUNCTION auto_generate_prompt_slug()
43
+ RETURNS TRIGGER AS $$
44
+ BEGIN
45
+ -- Only generate slug for public prompts if not already set
46
+ IF NEW.is_public = true AND (NEW.slug IS NULL OR NEW.slug = '') THEN
47
+ NEW.slug := generate_prompt_slug(NEW.title);
48
+ END IF;
49
+
50
+ RETURN NEW;
51
+ END;
52
+ $$ LANGUAGE plpgsql;
53
+
54
+ -- Create trigger
55
+ DROP TRIGGER IF EXISTS trigger_auto_generate_prompt_slug ON user_prompt;
56
+ CREATE TRIGGER trigger_auto_generate_prompt_slug
57
+ BEFORE INSERT OR UPDATE ON user_prompt
58
+ FOR EACH ROW
59
+ EXECUTE FUNCTION auto_generate_prompt_slug();
60
+
61
+ -- Backfill existing public prompts with slugs
62
+ UPDATE user_prompt
63
+ SET slug = generate_prompt_slug(title)
64
+ WHERE is_public = true AND (slug IS NULL OR slug = '');
@@ -0,0 +1,26 @@
1
+ -- ============================================================================
2
+ -- Update public_prompts_with_stats view to include slug
3
+ -- ============================================================================
4
+
5
+ -- Drop existing view to allow adding new column
6
+ DROP VIEW IF EXISTS public_prompts_with_stats CASCADE;
7
+
8
+ -- Recreate view with slug column
9
+ CREATE VIEW public_prompts_with_stats AS
10
+ SELECT
11
+ p.id,
12
+ p.owner_id,
13
+ p.slug,
14
+ p.title,
15
+ p.content,
16
+ p.type,
17
+ p.tags,
18
+ p.created_at,
19
+ COALESCE(s.views, 0) as views,
20
+ COALESCE(s.used_count, 0) as used_count,
21
+ COALESCE(s.picked_count, 0) as picked_count,
22
+ s.last_used_at
23
+ FROM user_prompt p
24
+ LEFT JOIN prompt_stats s ON s.prompt_id = p.id
25
+ WHERE p.is_public = true
26
+ ORDER BY s.picked_count DESC NULLS LAST, p.created_at DESC;
@@ -0,0 +1,33 @@
1
+ -- ============================================================================
2
+ -- Update public_prompts_with_stats view to include username
3
+ -- ============================================================================
4
+
5
+ -- Drop existing view to allow adding username column
6
+ DROP VIEW IF EXISTS public_prompts_with_stats CASCADE;
7
+
8
+ -- Recreate view with slug, username and avatar
9
+ CREATE VIEW public_prompts_with_stats AS
10
+ SELECT
11
+ p.id,
12
+ p.owner_id,
13
+ p.slug,
14
+ p.title,
15
+ p.content,
16
+ p.type,
17
+ p.tags,
18
+ p.created_at,
19
+ up.username,
20
+ up.avatar_url,
21
+ COALESCE(s.views, 0) as views,
22
+ COALESCE(s.used_count, 0) as used_count,
23
+ COALESCE(s.picked_count, 0) as picked_count,
24
+ s.last_used_at
25
+ FROM user_prompt p
26
+ LEFT JOIN prompt_stats s ON s.prompt_id = p.id
27
+ LEFT JOIN user_profil up ON up.owner_id = p.owner_id
28
+ WHERE p.is_public = true
29
+ ORDER BY s.picked_count DESC NULLS LAST, p.created_at DESC;
30
+
31
+ -- Grant permissions
32
+ GRANT SELECT ON public_prompts_with_stats TO authenticated;
33
+ GRANT SELECT ON public_prompts_with_stats TO anon;
@@ -0,0 +1,40 @@
1
+ -- ============================================================================
2
+ -- Add lang column to user_prompt and update view
3
+ -- ============================================================================
4
+
5
+ -- Add lang column to user_prompt
6
+ ALTER TABLE public.user_prompt
7
+ ADD COLUMN IF NOT EXISTS lang TEXT DEFAULT 'en';
8
+
9
+ -- Add comment
10
+ COMMENT ON COLUMN public.user_prompt.lang IS 'Language of the prompt (en, fr, etc.)';
11
+
12
+ -- Update view to include lang
13
+ DROP VIEW IF EXISTS public_prompts_with_stats CASCADE;
14
+
15
+ CREATE VIEW public_prompts_with_stats AS
16
+ SELECT
17
+ p.id,
18
+ p.owner_id,
19
+ p.slug,
20
+ p.title,
21
+ p.content,
22
+ p.type,
23
+ p.tags,
24
+ p.lang,
25
+ p.created_at,
26
+ up.username,
27
+ up.avatar_url,
28
+ COALESCE(s.views, 0) as views,
29
+ COALESCE(s.used_count, 0) as used_count,
30
+ COALESCE(s.picked_count, 0) as picked_count,
31
+ s.last_used_at
32
+ FROM user_prompt p
33
+ LEFT JOIN prompt_stats s ON s.prompt_id = p.id
34
+ LEFT JOIN user_profil up ON up.owner_id = p.owner_id
35
+ WHERE p.is_public = true
36
+ ORDER BY s.picked_count DESC NULLS LAST, p.created_at DESC;
37
+
38
+ -- Grant permissions
39
+ GRANT SELECT ON public_prompts_with_stats TO authenticated;
40
+ GRANT SELECT ON public_prompts_with_stats TO anon;
@@ -0,0 +1,92 @@
1
+ -- Migration: Extract model and prompt from metadata to dedicated columns in user_token_ledger
2
+ -- Modifies record_token_consumption RPC to populate model and prompt columns from p_meta JSONB
3
+
4
+ CREATE OR REPLACE FUNCTION public.record_token_consumption(
5
+ p_user_id uuid,
6
+ p_debit_tokens bigint,
7
+ p_provider_cost_usd numeric,
8
+ p_sell_usd numeric,
9
+ p_margin_usd numeric,
10
+ p_meta jsonb DEFAULT '{}'::jsonb
11
+ )
12
+ RETURNS jsonb AS $$
13
+ DECLARE
14
+ v_current_balance bigint;
15
+ v_wallet_provider_budget numeric;
16
+ v_wallet_sell_value numeric;
17
+ v_new_balance bigint;
18
+ v_allow_overdraft boolean;
19
+ BEGIN
20
+ -- 1. Get current balance
21
+ SELECT COALESCE(SUM(amount), 0) INTO v_current_balance
22
+ FROM public.user_token_ledger
23
+ WHERE owner_id = p_user_id;
24
+
25
+ -- 2. Get wallet values
26
+ SELECT
27
+ wallet_provider_budget_usd,
28
+ wallet_sell_value_usd
29
+ INTO v_wallet_provider_budget, v_wallet_sell_value
30
+ FROM public.user_token_wallet
31
+ WHERE user_id = p_user_id;
32
+
33
+ -- 3. Check overdraft
34
+ v_allow_overdraft := COALESCE((p_meta->>'allowOverdraft')::boolean, false);
35
+
36
+ IF NOT v_allow_overdraft AND v_current_balance < p_debit_tokens THEN
37
+ RAISE EXCEPTION 'Insufficient token balance: have %, need %', v_current_balance, p_debit_tokens;
38
+ END IF;
39
+
40
+ IF v_wallet_provider_budget < p_provider_cost_usd THEN
41
+ RAISE EXCEPTION 'Insufficient provider budget: have $%, need $%', v_wallet_provider_budget, p_provider_cost_usd;
42
+ END IF;
43
+
44
+ IF v_wallet_sell_value < p_sell_usd THEN
45
+ RAISE EXCEPTION 'Insufficient sell value: have $%, need $%', v_wallet_sell_value, p_sell_usd;
46
+ END IF;
47
+
48
+ -- 4. Insert ledger entry (negative amount) with model and prompt extracted from metadata
49
+ INSERT INTO public.user_token_ledger (
50
+ owner_id,
51
+ amount,
52
+ type,
53
+ debit_tokens,
54
+ provider_cost_usd,
55
+ sell_usd,
56
+ margin_usd,
57
+ model,
58
+ prompt,
59
+ meta
60
+ ) VALUES (
61
+ p_user_id,
62
+ -p_debit_tokens,
63
+ 'use',
64
+ p_debit_tokens,
65
+ p_provider_cost_usd,
66
+ p_sell_usd,
67
+ p_margin_usd,
68
+ p_meta->>'model',
69
+ substring(p_meta->>'prompt', 1, 32),
70
+ p_meta
71
+ );
72
+
73
+ -- 5. Update wallet (subtract USD values)
74
+ UPDATE public.user_token_wallet
75
+ SET
76
+ wallet_provider_budget_usd = wallet_provider_budget_usd - p_provider_cost_usd,
77
+ wallet_sell_value_usd = wallet_sell_value_usd - p_sell_usd,
78
+ updated_at = now()
79
+ WHERE user_id = p_user_id;
80
+
81
+ -- 6. Get new balance
82
+ v_new_balance := v_current_balance - p_debit_tokens;
83
+
84
+ RETURN jsonb_build_object(
85
+ 'success', true,
86
+ 'new_balance', v_new_balance
87
+ );
88
+ END;
89
+ $$ LANGUAGE plpgsql SECURITY DEFINER;
90
+
91
+ COMMENT ON FUNCTION public.record_token_consumption IS
92
+ 'Records AI consumption: debits tokens, updates wallet USD values, and extracts model/prompt from metadata to dedicated columns for efficient querying';
@@ -0,0 +1,64 @@
1
+ -- Fix duplicate purchases in ledger
2
+ -- This migration removes duplicate purchase entries with same payment_id
3
+ -- and recalculates wallet from clean ledger
4
+
5
+ BEGIN;
6
+
7
+ -- Step 1: Delete duplicate purchases (keep oldest entry per payment_id)
8
+ WITH duplicates AS (
9
+ SELECT
10
+ id,
11
+ owner_id,
12
+ meta->>'payment_id' as payment_id,
13
+ ts,
14
+ ROW_NUMBER() OVER (
15
+ PARTITION BY owner_id, meta->>'payment_id'
16
+ ORDER BY ts ASC
17
+ ) as rn
18
+ FROM user_token_ledger
19
+ WHERE
20
+ type = 'purchase'
21
+ AND meta->>'payment_id' IS NOT NULL
22
+ )
23
+ DELETE FROM user_token_ledger
24
+ WHERE id IN (
25
+ SELECT id FROM duplicates WHERE rn > 1
26
+ );
27
+
28
+ -- Step 2: Recalculate ALL wallets from clean ledger
29
+ WITH wallet_totals AS (
30
+ SELECT
31
+ owner_id,
32
+ SUM(CASE WHEN type = 'purchase' THEN provider_budget_added_usd ELSE 0 END) as total_provider_added,
33
+ SUM(CASE WHEN type = 'purchase' THEN sell_value_added_usd ELSE 0 END) as total_sell_added,
34
+ SUM(CASE WHEN type = 'use' THEN provider_cost_usd ELSE 0 END) as total_provider_used,
35
+ SUM(CASE WHEN type = 'use' THEN sell_usd ELSE 0 END) as total_sell_used
36
+ FROM user_token_ledger
37
+ GROUP BY owner_id
38
+ )
39
+ INSERT INTO user_token_wallet (
40
+ user_id,
41
+ wallet_provider_budget_usd,
42
+ wallet_sell_value_usd,
43
+ updated_at
44
+ )
45
+ SELECT
46
+ owner_id,
47
+ total_provider_added - COALESCE(total_provider_used, 0),
48
+ total_sell_added - COALESCE(total_sell_used, 0),
49
+ NOW()
50
+ FROM wallet_totals
51
+ ON CONFLICT (user_id) DO UPDATE SET
52
+ wallet_provider_budget_usd = EXCLUDED.wallet_provider_budget_usd,
53
+ wallet_sell_value_usd = EXCLUDED.wallet_sell_value_usd,
54
+ updated_at = NOW();
55
+
56
+ -- Step 3: Add unique constraint to prevent future duplicates
57
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ledger_unique_payment
58
+ ON user_token_ledger (owner_id, (meta->>'payment_id'))
59
+ WHERE type = 'purchase' AND meta->>'payment_id' IS NOT NULL;
60
+
61
+ COMMIT;
62
+
63
+ COMMENT ON INDEX idx_ledger_unique_payment IS
64
+ 'Prevents duplicate purchase entries for same payment_id';
@@ -0,0 +1,63 @@
1
+ -- module-ai: Set default text and image models GLOBALLY
2
+ -- This migration creates a system-wide config table for default models
3
+ -- All API calls (recipes, images, etc.) will use these defaults unless user overrides
4
+
5
+ begin;
6
+
7
+ -- -----------------------------------------------------------------------------
8
+ -- 1) Create global AI configuration table (single row)
9
+ -- -----------------------------------------------------------------------------
10
+
11
+ create table if not exists public.ai_global_settings (
12
+ id integer primary key default 1 check (id = 1), -- Only 1 row allowed
13
+
14
+ -- Default models for all users
15
+ default_text_provider text not null default 'openai',
16
+ default_text_model text not null default 'gpt-4.1-mini',
17
+
18
+ default_image_provider text not null default 'google',
19
+ default_image_model text not null default 'gemini-2.5-flash-image',
20
+
21
+ -- Optional: default max tokens
22
+ max_tokens_per_call integer not null default 16384,
23
+
24
+ -- Metadata
25
+ updated_at timestamptz not null default now(),
26
+ updated_by uuid null references auth.users(id)
27
+ );
28
+
29
+ -- Insert default values
30
+ insert into public.ai_global_settings (id)
31
+ values (1)
32
+ on conflict (id) do update
33
+ set
34
+ default_text_provider = excluded.default_text_provider,
35
+ default_text_model = excluded.default_text_model,
36
+ default_image_provider = excluded.default_image_provider,
37
+ default_image_model = excluded.default_image_model,
38
+ max_tokens_per_call = excluded.max_tokens_per_call;
39
+
40
+ -- -----------------------------------------------------------------------------
41
+ -- 2) RLS: Public read for all, only superadmin can write
42
+ -- -----------------------------------------------------------------------------
43
+
44
+ alter table public.ai_global_settings enable row level security;
45
+
46
+ drop policy if exists ai_global_settings_select on public.ai_global_settings;
47
+ create policy ai_global_settings_select
48
+ on public.ai_global_settings for select
49
+ using (true); -- Everyone can read
50
+
51
+ drop policy if exists ai_global_settings_update on public.ai_global_settings;
52
+ create policy ai_global_settings_update
53
+ on public.ai_global_settings for update
54
+ using (is_superadmin(auth.uid()))
55
+ with check (is_superadmin(auth.uid()));
56
+
57
+ -- Updated trigger
58
+ drop trigger if exists trg_ai_global_settings_updated_at on public.ai_global_settings;
59
+ create trigger trg_ai_global_settings_updated_at
60
+ before update on public.ai_global_settings
61
+ for each row execute function public.set_updated_at();
62
+
63
+ commit;
@@ -0,0 +1,17 @@
1
+ -- module-ai: Remove deprecated tables ai_providers and ai_provider_models
2
+ -- These tables are no longer used - we query Vercel AI Gateway directly for available models
3
+ -- Global defaults are now stored in ai_global_settings table
4
+
5
+ begin;
6
+
7
+ -- Drop dependent tables/views first (if any)
8
+ drop view if exists ai_active_models_v cascade;
9
+
10
+ -- Drop the tables
11
+ drop table if exists public.ai_provider_models cascade;
12
+ drop table if exists public.ai_providers cascade;
13
+
14
+ -- Note: No need to update ai_global_settings as it doesn't have foreign keys to these tables
15
+ -- The global settings table uses simple text fields for provider/model names
16
+
17
+ commit;
@@ -0,0 +1,34 @@
1
+ -- ===========================================================================
2
+ -- Module: @lastbrain/module-ai
3
+ -- Migration DOWN: 20251217120000_user_token_quota_monthly.sql
4
+ -- Description: Rollback monthly token quota tables
5
+ -- ===========================================================================
6
+
7
+ -- Drop helper function
8
+ DROP FUNCTION IF EXISTS public.get_plan_default_tokens(text);
9
+
10
+ -- Drop triggers
11
+ DROP TRIGGER IF EXISTS set_user_token_quota_monthly_updated_at ON public.user_token_quota_monthly;
12
+
13
+ -- Drop policies - user_token_quota_ledger
14
+ DROP POLICY IF EXISTS user_token_quota_ledger_admin_all ON public.user_token_quota_ledger;
15
+ DROP POLICY IF EXISTS user_token_quota_ledger_select_own ON public.user_token_quota_ledger;
16
+
17
+ -- Drop policies - user_token_quota_monthly
18
+ DROP POLICY IF EXISTS user_token_quota_monthly_admin_all ON public.user_token_quota_monthly;
19
+ DROP POLICY IF EXISTS user_token_quota_monthly_select_own ON public.user_token_quota_monthly;
20
+
21
+ -- Drop indexes - user_token_quota_ledger
22
+ DROP INDEX IF EXISTS public.idx_user_token_quota_ledger_period;
23
+ DROP INDEX IF EXISTS public.idx_user_token_quota_ledger_owner_created;
24
+ DROP INDEX IF EXISTS public.idx_user_token_quota_ledger_owner;
25
+
26
+ -- Drop indexes - user_token_quota_monthly
27
+ DROP INDEX IF EXISTS public.idx_user_token_quota_monthly_subscription;
28
+ DROP INDEX IF EXISTS public.idx_user_token_quota_monthly_period;
29
+ DROP INDEX IF EXISTS public.idx_user_token_quota_monthly_plan;
30
+ DROP INDEX IF EXISTS public.idx_user_token_quota_monthly_owner;
31
+
32
+ -- Drop tables
33
+ DROP TABLE IF EXISTS public.user_token_quota_ledger;
34
+ DROP TABLE IF EXISTS public.user_token_quota_monthly;