insforge 1.2.10 → 1.4.8

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 (506) hide show
  1. package/.claude-plugin/marketplace.json +20 -20
  2. package/.dockerignore +60 -60
  3. package/.env.example +83 -77
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +65 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -63
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +46 -44
  15. package/CLAUDE_PLUGIN.md +104 -104
  16. package/CODE_OF_CONDUCT.md +128 -128
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -30
  19. package/GITHUB_OAUTH_SETUP.md +49 -49
  20. package/GOOGLE_OAUTH_SETUP.md +148 -148
  21. package/LICENSE +201 -201
  22. package/README.md +182 -182
  23. package/assets/Dark.svg +23 -23
  24. package/auth/package.json +30 -28
  25. package/auth/src/lib/broadcastService.ts +4 -4
  26. package/auth/src/lib/insforge.ts +8 -0
  27. package/auth/src/main.tsx +2 -4
  28. package/auth/src/pages/SignInPage.tsx +5 -2
  29. package/auth/src/pages/SignUpPage.tsx +5 -2
  30. package/auth/src/pages/VerifyEmailPage.tsx +18 -0
  31. package/auth/tsconfig.json +33 -32
  32. package/auth/tsconfig.node.json +11 -11
  33. package/backend/package.json +82 -75
  34. package/backend/src/api/middlewares/rate-limiters.ts +127 -127
  35. package/backend/src/api/routes/ai/index.routes.ts +475 -468
  36. package/backend/src/api/routes/auth/index.routes.ts +720 -570
  37. package/backend/src/api/routes/auth/oauth.routes.ts +478 -448
  38. package/backend/src/api/routes/database/advance.routes.ts +37 -16
  39. package/backend/src/api/routes/database/index.routes.ts +80 -1
  40. package/backend/src/api/routes/database/records.routes.ts +48 -184
  41. package/backend/src/api/routes/database/rpc.routes.ts +69 -0
  42. package/backend/src/api/routes/database/tables.routes.ts +0 -14
  43. package/backend/src/api/routes/deployments/index.routes.ts +192 -0
  44. package/backend/src/api/routes/docs/index.routes.ts +76 -76
  45. package/backend/src/api/routes/email/index.routes.ts +35 -0
  46. package/backend/src/api/routes/functions/index.routes.ts +21 -15
  47. package/backend/src/api/routes/metadata/index.routes.ts +38 -0
  48. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  49. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  50. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  51. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  52. package/backend/src/api/routes/storage/index.routes.ts +18 -12
  53. package/backend/src/api/routes/usage/index.routes.ts +6 -4
  54. package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
  55. package/backend/src/infra/database/database.manager.ts +14 -11
  56. package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
  57. package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
  58. package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
  59. package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
  60. package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
  61. package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
  62. package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
  63. package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
  64. package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
  65. package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
  66. package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
  67. package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
  68. package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
  69. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
  70. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
  71. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
  72. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
  73. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  74. package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
  75. package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
  76. package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
  77. package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
  78. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  79. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  80. package/backend/src/infra/security/token.manager.ts +216 -125
  81. package/backend/src/infra/socket/socket.manager.ts +198 -64
  82. package/backend/src/providers/ai/openrouter.provider.ts +24 -12
  83. package/backend/src/providers/database/base.provider.ts +39 -0
  84. package/backend/src/providers/database/cloud.provider.ts +159 -0
  85. package/backend/src/providers/deployments/vercel.provider.ts +516 -0
  86. package/backend/src/providers/email/base.provider.ts +4 -7
  87. package/backend/src/providers/email/cloud.provider.ts +84 -0
  88. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  89. package/backend/src/providers/oauth/index.ts +1 -0
  90. package/backend/src/server.ts +329 -284
  91. package/backend/src/services/ai/ai-config.service.ts +6 -6
  92. package/backend/src/services/ai/ai-model.service.ts +60 -60
  93. package/backend/src/services/ai/ai-usage.service.ts +7 -7
  94. package/backend/src/services/ai/chat-completion.service.ts +415 -220
  95. package/backend/src/services/ai/helpers.ts +64 -64
  96. package/backend/src/services/ai/image-generation.service.ts +3 -3
  97. package/backend/src/services/ai/index.ts +13 -13
  98. package/backend/src/services/auth/auth-config.service.ts +4 -4
  99. package/backend/src/services/auth/auth-otp.service.ts +6 -6
  100. package/backend/src/services/auth/auth.service.ts +148 -74
  101. package/backend/src/services/auth/index.ts +4 -4
  102. package/backend/src/services/auth/oauth-config.service.ts +12 -12
  103. package/backend/src/services/database/database-advance.service.ts +19 -55
  104. package/backend/src/services/database/database-table.service.ts +38 -94
  105. package/backend/src/services/database/database.service.ts +127 -0
  106. package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
  107. package/backend/src/services/deployments/deployment.service.ts +693 -0
  108. package/backend/src/services/email/email.service.ts +5 -7
  109. package/backend/src/services/functions/function.service.ts +61 -41
  110. package/backend/src/services/logs/audit.service.ts +10 -10
  111. package/backend/src/services/realtime/index.ts +3 -0
  112. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  113. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  114. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  115. package/backend/src/services/secrets/secret.service.ts +101 -27
  116. package/backend/src/services/storage/storage.service.ts +30 -30
  117. package/backend/src/services/usage/usage.service.ts +6 -6
  118. package/backend/src/types/ai.ts +8 -0
  119. package/backend/src/types/auth.ts +16 -1
  120. package/backend/src/types/database.ts +2 -0
  121. package/backend/src/types/deployments.ts +33 -0
  122. package/backend/src/types/realtime.ts +18 -0
  123. package/backend/src/types/socket.ts +7 -31
  124. package/backend/src/types/storage.ts +1 -1
  125. package/backend/src/types/webhooks.ts +45 -0
  126. package/backend/src/utils/cookies.ts +34 -0
  127. package/backend/src/utils/environment.ts +0 -14
  128. package/backend/src/utils/s3-config-loader.ts +64 -0
  129. package/backend/src/utils/seed.ts +79 -43
  130. package/backend/src/utils/sql-parser.ts +216 -0
  131. package/backend/src/utils/utils.ts +114 -114
  132. package/backend/src/utils/validations.ts +10 -10
  133. package/backend/tests/README.md +133 -133
  134. package/backend/tests/cleanup-all-test-data.sh +230 -230
  135. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  136. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  137. package/backend/tests/local/test-ai-config.sh +129 -129
  138. package/backend/tests/local/test-ai-usage.sh +80 -80
  139. package/backend/tests/local/test-auth-router.sh +143 -143
  140. package/backend/tests/local/test-database-router.sh +222 -222
  141. package/backend/tests/local/test-e2e.sh +240 -240
  142. package/backend/tests/local/test-fk-errors.sh +96 -96
  143. package/backend/tests/local/test-functions.sh +123 -123
  144. package/backend/tests/local/test-id-field.sh +200 -200
  145. package/backend/tests/local/test-logs.sh +132 -132
  146. package/backend/tests/local/test-public-bucket.sh +264 -264
  147. package/backend/tests/local/test-rpc.sh +141 -0
  148. package/backend/tests/local/test-secrets.sh +249 -249
  149. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  150. package/backend/tests/local/test-traditional-rest.sh +208 -208
  151. package/backend/tests/manual/README.md +50 -50
  152. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  153. package/backend/tests/manual/seed-large-table.sql +100 -100
  154. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  155. package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
  156. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  157. package/backend/tests/manual/test-database-advance.sh +296 -296
  158. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  159. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  160. package/backend/tests/manual/test-rawsql-modes.sh +244 -244
  161. package/backend/tests/manual/test-universal-storage.sh +263 -263
  162. package/backend/tests/manual/test-users.sql +17 -17
  163. package/backend/tests/run-all-tests.sh +139 -139
  164. package/backend/tests/setup.ts +0 -0
  165. package/backend/tests/test-config.sh +338 -338
  166. package/backend/tests/unit/analyze-query.test.ts +697 -0
  167. package/backend/tests/unit/database-advance.test.ts +326 -0
  168. package/backend/tests/unit/helpers.test.ts +2 -2
  169. package/backend/tsconfig.json +22 -22
  170. package/claude-plugin/.claude-plugin/plugin.json +24 -24
  171. package/claude-plugin/README.md +133 -133
  172. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +273 -270
  173. package/docker-compose.prod.yml +204 -200
  174. package/docker-compose.yml +232 -228
  175. package/docker-init/db/db-init.sql +97 -97
  176. package/docker-init/db/jwt.sql +5 -5
  177. package/docker-init/db/postgresql.conf +16 -16
  178. package/docker-init/logs/vector.yml +236 -236
  179. package/docs/README.md +44 -44
  180. package/docs/agent-docs/deployment.md +79 -0
  181. package/docs/agent-docs/real-time.md +269 -0
  182. package/docs/changelog.mdx +212 -67
  183. package/docs/core-concepts/ai/architecture.mdx +350 -372
  184. package/docs/core-concepts/ai/sdk.mdx +238 -213
  185. package/docs/core-concepts/authentication/architecture.mdx +276 -278
  186. package/docs/core-concepts/authentication/sdk.mdx +710 -414
  187. package/docs/core-concepts/authentication/ui-components/customization.mdx +733 -529
  188. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +247 -221
  189. package/docs/core-concepts/authentication/ui-components/react-router.mdx +183 -184
  190. package/docs/core-concepts/authentication/ui-components/react.mdx +136 -129
  191. package/docs/core-concepts/database/architecture.mdx +292 -255
  192. package/docs/core-concepts/database/pgvector.mdx +138 -0
  193. package/docs/core-concepts/database/sdk.mdx +382 -382
  194. package/docs/core-concepts/deployments/architecture.mdx +152 -0
  195. package/docs/core-concepts/email/architecture.mdx +103 -0
  196. package/docs/core-concepts/email/sdk.mdx +53 -0
  197. package/docs/core-concepts/functions/architecture.mdx +105 -105
  198. package/docs/core-concepts/functions/sdk.mdx +183 -184
  199. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  200. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  201. package/docs/core-concepts/storage/architecture.mdx +243 -243
  202. package/docs/core-concepts/storage/sdk.mdx +253 -253
  203. package/docs/deployment/README.md +94 -94
  204. package/docs/deployment/deploy-to-aws-ec2.md +564 -564
  205. package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
  206. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
  207. package/docs/deployment/deploy-to-render.md +441 -441
  208. package/docs/deprecated/insforge-auth-api.md +214 -214
  209. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  210. package/docs/deprecated/insforge-db-api.md +358 -358
  211. package/docs/deprecated/insforge-db-sdk.md +139 -139
  212. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  213. package/docs/deprecated/insforge-debug.md +64 -64
  214. package/docs/deprecated/insforge-instructions.md +123 -123
  215. package/docs/deprecated/insforge-project.md +117 -117
  216. package/docs/deprecated/insforge-storage-api.md +278 -278
  217. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  218. package/docs/docs.json +240 -210
  219. package/docs/examples/framework-guides/nextjs.mdx +131 -131
  220. package/docs/examples/framework-guides/nuxt.mdx +165 -165
  221. package/docs/examples/framework-guides/react.mdx +165 -165
  222. package/docs/examples/framework-guides/svelte.mdx +153 -153
  223. package/docs/examples/framework-guides/vue.mdx +159 -159
  224. package/docs/examples/overview.mdx +67 -67
  225. package/docs/favicon.png +0 -0
  226. package/docs/favicon.svg +4 -19
  227. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  228. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  229. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  230. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  231. package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
  232. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  233. package/docs/images/changelog/dec-2025/moreModels.png +0 -0
  234. package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
  235. package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
  236. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  237. package/docs/images/changelog/dec-2025/realtime2.png +0 -0
  238. package/docs/images/icons/ai.svg +4 -4
  239. package/docs/images/logos/nextjs.svg +4 -4
  240. package/docs/images/logos/nuxt.svg +4 -4
  241. package/docs/images/logos/react.svg +5 -5
  242. package/docs/images/logos/svelte.svg +4 -4
  243. package/docs/images/logos/vue.svg +5 -5
  244. package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
  245. package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
  246. package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
  247. package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
  248. package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
  249. package/docs/images/mcp-setup/claude-code-connect.png +0 -0
  250. package/docs/images/mcp-setup/cline-1.png +0 -0
  251. package/docs/images/mcp-setup/cline-2.png +0 -0
  252. package/docs/images/mcp-setup/cline-3.png +0 -0
  253. package/docs/images/mcp-setup/connect-project.png +0 -0
  254. package/docs/images/mcp-setup/copilot-1.png +0 -0
  255. package/docs/images/mcp-setup/copilot-2.png +0 -0
  256. package/docs/images/mcp-setup/copilot-3.png +0 -0
  257. package/docs/images/mcp-setup/mcp-json-1.png +0 -0
  258. package/docs/images/mcp-setup/mcp-json-2.png +0 -0
  259. package/docs/images/mcp-setup/qoder-1.png +0 -0
  260. package/docs/images/mcp-setup/qoder-2.png +0 -0
  261. package/docs/images/mcp-setup/roocode-1.png +0 -0
  262. package/docs/images/mcp-setup/roocode-2.png +0 -0
  263. package/docs/images/mcp-setup/trae-1.png +0 -0
  264. package/docs/images/mcp-setup/trae-2.png +0 -0
  265. package/docs/images/mcp-setup/trae-3.png +0 -0
  266. package/docs/images/mcp-setup/trae-4.png +0 -0
  267. package/docs/images/mcp-setup/trae-5.png +0 -0
  268. package/docs/images/mcp-setup/windsurf-1.png +0 -0
  269. package/docs/images/mcp-setup/windsurf-2.png +0 -0
  270. package/docs/insforge-instructions-sdk.md +93 -88
  271. package/docs/introduction.mdx +46 -45
  272. package/docs/logo/dark.svg +22 -22
  273. package/docs/logo/light.svg +20 -20
  274. package/docs/mcp-setup.mdx +332 -0
  275. package/docs/oauth-server.mdx +563 -0
  276. package/docs/partnership.mdx +720 -646
  277. package/docs/quickstart.mdx +82 -82
  278. package/docs/showcase.mdx +52 -52
  279. package/docs/snippets/sdk-installation.mdx +21 -21
  280. package/docs/snippets/service-icons.mdx +27 -27
  281. package/docs/vscode-extension.mdx +74 -0
  282. package/eslint.config.js +1 -0
  283. package/examples/oauth/frontend-oauth-example.html +250 -250
  284. package/examples/response-examples.md +443 -443
  285. package/frontend/components.json +17 -17
  286. package/frontend/package.json +69 -69
  287. package/frontend/src/App.tsx +8 -3
  288. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  289. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  290. package/frontend/src/assets/icons/checked.svg +3 -3
  291. package/frontend/src/assets/icons/connected.svg +3 -3
  292. package/frontend/src/assets/icons/error.svg +3 -3
  293. package/frontend/src/assets/icons/loader.svg +9 -9
  294. package/frontend/src/assets/icons/pencil.svg +4 -4
  295. package/frontend/src/assets/icons/refresh.svg +4 -4
  296. package/frontend/src/assets/icons/step_active.svg +3 -3
  297. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  298. package/frontend/src/assets/icons/warning.svg +3 -3
  299. package/frontend/src/assets/logos/antigravity.svg +1 -0
  300. package/frontend/src/assets/logos/apple.svg +3 -3
  301. package/frontend/src/assets/logos/claude_code.svg +3 -3
  302. package/frontend/src/assets/logos/cline.svg +6 -6
  303. package/frontend/src/assets/logos/copilot.svg +10 -0
  304. package/frontend/src/assets/logos/cursor.svg +20 -20
  305. package/frontend/src/assets/logos/deepseek.svg +139 -0
  306. package/frontend/src/assets/logos/discord.svg +8 -8
  307. package/frontend/src/assets/logos/facebook.svg +3 -3
  308. package/frontend/src/assets/logos/gemini.svg +19 -19
  309. package/frontend/src/assets/logos/github.svg +5 -5
  310. package/frontend/src/assets/logos/google.svg +13 -13
  311. package/frontend/src/assets/logos/grok.svg +10 -10
  312. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  313. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  314. package/frontend/src/assets/logos/instagram.svg +1 -1
  315. package/frontend/src/assets/logos/kiro.svg +9 -0
  316. package/frontend/src/assets/logos/linkedin.svg +3 -3
  317. package/frontend/src/assets/logos/openai.svg +10 -10
  318. package/frontend/src/assets/logos/qoder.svg +4 -0
  319. package/frontend/src/assets/logos/qwen.svg +15 -0
  320. package/frontend/src/assets/logos/roo_code.svg +9 -9
  321. package/frontend/src/assets/logos/spotify.svg +16 -16
  322. package/frontend/src/assets/logos/tiktok.svg +5 -5
  323. package/frontend/src/assets/logos/trae.svg +3 -3
  324. package/frontend/src/assets/logos/windsurf.svg +10 -10
  325. package/frontend/src/assets/logos/x.svg +3 -3
  326. package/frontend/src/components/CodeBlock.tsx +2 -2
  327. package/frontend/src/components/ConnectCTA.tsx +3 -2
  328. package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
  329. package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
  330. package/frontend/src/components/datagrid/index.ts +1 -1
  331. package/frontend/src/components/index.ts +0 -1
  332. package/frontend/src/components/layout/AppHeader.tsx +13 -37
  333. package/frontend/src/components/layout/AppSidebar.tsx +85 -100
  334. package/frontend/src/components/layout/Layout.tsx +34 -32
  335. package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
  336. package/frontend/src/components/radix/Select.tsx +151 -151
  337. package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
  338. package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
  339. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
  340. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
  341. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
  342. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
  343. package/frontend/src/features/ai/components/index.ts +6 -6
  344. package/frontend/src/features/ai/helpers.ts +147 -141
  345. package/frontend/src/features/ai/{page → pages}/AIPage.tsx +166 -166
  346. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
  347. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
  348. package/frontend/src/features/auth/components/UsersDataGrid.tsx +61 -31
  349. package/frontend/src/features/auth/components/index.ts +5 -5
  350. package/frontend/src/features/auth/helpers.tsx +8 -0
  351. package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +275 -275
  352. package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
  353. package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +1 -1
  354. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
  355. package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
  356. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
  357. package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
  358. package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
  359. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  360. package/frontend/src/features/database/components/TableForm.tsx +0 -4
  361. package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
  362. package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
  363. package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
  364. package/frontend/src/features/database/constants.ts +16 -28
  365. package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
  366. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  367. package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
  368. package/frontend/src/features/database/hooks/useTables.ts +30 -28
  369. package/frontend/src/features/database/index.ts +1 -0
  370. package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -42
  371. package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +34 -51
  372. package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +42 -58
  373. package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +2 -2
  374. package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
  375. package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +34 -51
  376. package/frontend/src/features/database/services/advance.service.ts +1 -41
  377. package/frontend/src/features/database/services/database.service.ts +55 -0
  378. package/frontend/src/features/database/services/record.service.ts +4 -20
  379. package/frontend/src/features/database/services/table.service.ts +1 -10
  380. package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
  381. package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
  382. package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
  383. package/frontend/src/features/database/templates/notion-clone.ts +8 -8
  384. package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
  385. package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
  386. package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
  387. package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
  388. package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
  389. package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
  390. package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
  391. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
  392. package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
  393. package/frontend/src/features/functions/components/index.ts +5 -5
  394. package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
  395. package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
  396. package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
  397. package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +118 -116
  398. package/frontend/src/features/functions/services/function.service.ts +8 -25
  399. package/frontend/src/features/functions/services/secret.service.ts +23 -41
  400. package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +125 -118
  401. package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
  402. package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
  403. package/frontend/src/features/logs/components/index.ts +1 -0
  404. package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
  405. package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +36 -6
  406. package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
  407. package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
  408. package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
  409. package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
  410. package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
  411. package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
  412. package/frontend/src/features/onboard/components/index.ts +9 -4
  413. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
  414. package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
  415. package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
  416. package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
  417. package/frontend/src/features/onboard/index.ts +17 -13
  418. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  419. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  420. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  421. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  422. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  423. package/frontend/src/features/realtime/index.ts +11 -0
  424. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  425. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  426. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  427. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  428. package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
  429. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
  430. package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
  431. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +24 -11
  432. package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +11 -36
  433. package/frontend/src/index.css +249 -249
  434. package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
  435. package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
  436. package/frontend/src/lib/hooks/useMetadata.ts +45 -1
  437. package/frontend/src/lib/hooks/useModal.tsx +2 -0
  438. package/frontend/src/lib/routing/AppRoutes.tsx +103 -84
  439. package/frontend/src/lib/services/metadata.service.ts +20 -3
  440. package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
  441. package/frontend/src/lib/utils/menuItems.ts +223 -183
  442. package/frontend/src/lib/utils/utils.ts +196 -183
  443. package/frontend/tsconfig.json +25 -25
  444. package/frontend/tsconfig.node.json +9 -9
  445. package/functions/deno.json +24 -24
  446. package/functions/server.ts +6 -6
  447. package/functions/worker-template.js +1 -1
  448. package/i18n/README.ar.md +130 -130
  449. package/i18n/README.de.md +130 -130
  450. package/i18n/README.es.md +154 -154
  451. package/i18n/README.fr.md +134 -134
  452. package/i18n/README.hi.md +129 -129
  453. package/i18n/README.ja.md +174 -174
  454. package/i18n/README.ko.md +136 -136
  455. package/i18n/README.pt-BR.md +131 -131
  456. package/i18n/README.ru.md +129 -129
  457. package/i18n/README.zh-CN.md +133 -133
  458. package/openapi/ai.yaml +825 -715
  459. package/openapi/auth.yaml +1324 -1244
  460. package/openapi/email.yaml +158 -0
  461. package/openapi/functions.yaml +475 -475
  462. package/openapi/health.yaml +29 -29
  463. package/openapi/logs.yaml +221 -223
  464. package/openapi/metadata.yaml +175 -177
  465. package/openapi/realtime.yaml +699 -0
  466. package/openapi/records.yaml +381 -381
  467. package/openapi/secrets.yaml +370 -370
  468. package/openapi/storage.yaml +875 -875
  469. package/openapi/tables.yaml +462 -463
  470. package/package.json +97 -97
  471. package/shared-schemas/package.json +31 -31
  472. package/shared-schemas/src/ai-api.schema.ts +251 -143
  473. package/shared-schemas/src/ai.schema.ts +8 -4
  474. package/shared-schemas/src/auth-api.schema.ts +380 -339
  475. package/shared-schemas/src/auth.schema.ts +18 -11
  476. package/shared-schemas/src/cloud-events.schema.ts +26 -0
  477. package/shared-schemas/src/database-api.schema.ts +32 -1
  478. package/shared-schemas/src/database.schema.ts +39 -0
  479. package/shared-schemas/src/deployments-api.schema.ts +55 -0
  480. package/shared-schemas/src/deployments.schema.ts +30 -0
  481. package/shared-schemas/src/docs.schema.ts +32 -0
  482. package/shared-schemas/src/email-api.schema.ts +30 -0
  483. package/shared-schemas/src/functions-api.schema.ts +13 -4
  484. package/shared-schemas/src/functions.schema.ts +1 -1
  485. package/shared-schemas/src/index.ts +22 -14
  486. package/shared-schemas/src/metadata.schema.ts +39 -4
  487. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  488. package/shared-schemas/src/realtime.schema.ts +143 -0
  489. package/shared-schemas/src/secrets-api.schema.ts +44 -0
  490. package/shared-schemas/src/secrets.schema.ts +15 -0
  491. package/shared-schemas/tsconfig.json +21 -21
  492. package/tsconfig.json +7 -7
  493. package/zeabur/README.md +26 -13
  494. package/zeabur/template.yml +1001 -1032
  495. package/.cursor/rules/cursor-rules.mdc +0 -94
  496. package/backend/src/types/profile.ts +0 -55
  497. package/frontend/src/components/ProjectInfoModal.tsx +0 -128
  498. package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
  499. package/test-gemini.sh +0 -35
  500. package/test-usage-admin.sh +0 -57
  501. package/test-usage.sh +0 -50
  502. /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
  503. /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
  504. /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
  505. /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
  506. /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
@@ -0,0 +1,516 @@
1
+ import axios from 'axios';
2
+ import jwt from 'jsonwebtoken';
3
+ import crypto from 'crypto';
4
+ import { isCloudEnvironment } from '@/utils/environment.js';
5
+ import { AppError } from '@/api/middlewares/error.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import { SecretService } from '@/services/secrets/secret.service.js';
8
+ import logger from '@/utils/logger.js';
9
+
10
+ interface CloudCredentialsResponse {
11
+ team_id: string;
12
+ vercel_project_id: string;
13
+ bearer_token: string;
14
+ expires_at: string;
15
+ webhook_secret: string | null;
16
+ }
17
+
18
+ interface VercelCredentials {
19
+ token: string;
20
+ teamId: string;
21
+ projectId: string;
22
+ expiresAt: Date | null;
23
+ }
24
+
25
+ export interface VercelDeploymentResult {
26
+ id: string;
27
+ url: string | null;
28
+ state: string;
29
+ readyState: string;
30
+ name: string;
31
+ createdAt: Date;
32
+ error?: {
33
+ code: string;
34
+ message: string;
35
+ };
36
+ }
37
+
38
+ export interface CreateDeploymentOptions {
39
+ name?: string;
40
+ files?: Array<{
41
+ file: string;
42
+ sha: string;
43
+ size: number;
44
+ }>;
45
+ projectSettings?: {
46
+ buildCommand?: string | null;
47
+ outputDirectory?: string | null;
48
+ installCommand?: string | null;
49
+ devCommand?: string | null;
50
+ rootDirectory?: string | null;
51
+ };
52
+ meta?: Record<string, string>;
53
+ }
54
+
55
+ export interface DeploymentFile {
56
+ path: string;
57
+ content: Buffer;
58
+ sha: string;
59
+ size: number;
60
+ }
61
+
62
+ export class VercelProvider {
63
+ private static instance: VercelProvider;
64
+ private cloudCredentials: VercelCredentials | undefined;
65
+ private fetchPromise: Promise<VercelCredentials> | null = null;
66
+ private secretService: SecretService;
67
+
68
+ private constructor() {
69
+ this.secretService = SecretService.getInstance();
70
+ }
71
+
72
+ static getInstance(): VercelProvider {
73
+ if (!VercelProvider.instance) {
74
+ VercelProvider.instance = new VercelProvider();
75
+ }
76
+ return VercelProvider.instance;
77
+ }
78
+
79
+ /**
80
+ * Get Vercel credentials based on environment
81
+ */
82
+ async getCredentials(): Promise<VercelCredentials> {
83
+ if (isCloudEnvironment()) {
84
+ if (
85
+ this.cloudCredentials &&
86
+ (!this.cloudCredentials.expiresAt || new Date() < this.cloudCredentials.expiresAt)
87
+ ) {
88
+ return this.cloudCredentials;
89
+ }
90
+ return await this.fetchCloudCredentials();
91
+ }
92
+
93
+ const token = process.env.VERCEL_TOKEN;
94
+ const teamId = process.env.VERCEL_TEAM_ID;
95
+ const projectId = process.env.VERCEL_PROJECT_ID;
96
+
97
+ if (!token) {
98
+ throw new AppError(
99
+ 'VERCEL_TOKEN not found in environment variables',
100
+ 500,
101
+ ERROR_CODES.INTERNAL_ERROR
102
+ );
103
+ }
104
+ if (!teamId) {
105
+ throw new AppError(
106
+ 'VERCEL_TEAM_ID not found in environment variables',
107
+ 500,
108
+ ERROR_CODES.INTERNAL_ERROR
109
+ );
110
+ }
111
+ if (!projectId) {
112
+ throw new AppError(
113
+ 'VERCEL_PROJECT_ID not found in environment variables',
114
+ 500,
115
+ ERROR_CODES.INTERNAL_ERROR
116
+ );
117
+ }
118
+
119
+ return { token, teamId, projectId, expiresAt: null };
120
+ }
121
+
122
+ /**
123
+ * Check if Vercel is properly configured
124
+ */
125
+ isConfigured(): boolean {
126
+ if (isCloudEnvironment()) {
127
+ return true;
128
+ }
129
+ return !!(
130
+ process.env.VERCEL_TOKEN &&
131
+ process.env.VERCEL_TEAM_ID &&
132
+ process.env.VERCEL_PROJECT_ID
133
+ );
134
+ }
135
+
136
+ /**
137
+ * Fetch credentials from cloud service
138
+ */
139
+ private async fetchCloudCredentials(): Promise<VercelCredentials> {
140
+ if (this.fetchPromise) {
141
+ logger.info('Vercel credentials fetch already in progress, waiting for completion...');
142
+ return this.fetchPromise;
143
+ }
144
+
145
+ this.fetchPromise = (async () => {
146
+ try {
147
+ const projectId = process.env.PROJECT_ID;
148
+ if (!projectId) {
149
+ throw new Error('PROJECT_ID not found in environment variables');
150
+ }
151
+
152
+ const jwtSecret = process.env.JWT_SECRET;
153
+ if (!jwtSecret) {
154
+ throw new Error('JWT_SECRET not found in environment variables');
155
+ }
156
+
157
+ const signature = jwt.sign({ projectId }, jwtSecret, { expiresIn: '1h' });
158
+
159
+ const response = await fetch(
160
+ `${process.env.CLOUD_API_HOST || 'https://api.insforge.dev'}/sites/v1/credentials/${projectId}?sign=${signature}`
161
+ );
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`Failed to fetch Vercel credentials: ${response.statusText}`);
165
+ }
166
+
167
+ const data = (await response.json()) as CloudCredentialsResponse;
168
+
169
+ if (!data.bearer_token || !data.vercel_project_id) {
170
+ throw new Error('Invalid response: missing Vercel credentials');
171
+ }
172
+
173
+ if (data.webhook_secret) {
174
+ await this.storeWebhookSecret(data.webhook_secret);
175
+ }
176
+
177
+ this.cloudCredentials = {
178
+ token: data.bearer_token,
179
+ teamId: data.team_id,
180
+ projectId: data.vercel_project_id,
181
+ expiresAt: new Date(data.expires_at),
182
+ };
183
+
184
+ logger.info('Successfully fetched Vercel credentials from cloud', {
185
+ expiresAt: this.cloudCredentials.expiresAt?.toISOString(),
186
+ });
187
+
188
+ return this.cloudCredentials;
189
+ } catch (error) {
190
+ logger.error('Failed to fetch Vercel credentials', {
191
+ error: error instanceof Error ? error.message : String(error),
192
+ });
193
+ throw error;
194
+ } finally {
195
+ this.fetchPromise = null;
196
+ }
197
+ })();
198
+
199
+ return this.fetchPromise;
200
+ }
201
+
202
+ /**
203
+ * Store webhook secret in secrets service
204
+ */
205
+ private async storeWebhookSecret(webhookSecret: string): Promise<void> {
206
+ const secretKey = 'VERCEL_WEBHOOK_SECRET';
207
+
208
+ try {
209
+ const existingSecret = await this.secretService.getSecretByKey(secretKey);
210
+
211
+ if (existingSecret === webhookSecret) {
212
+ return;
213
+ }
214
+
215
+ if (existingSecret !== null) {
216
+ await this.secretService.updateSecretByKey(secretKey, { value: webhookSecret });
217
+ logger.info('Vercel webhook secret updated');
218
+ } else {
219
+ await this.secretService.createSecret({
220
+ key: secretKey,
221
+ value: webhookSecret,
222
+ isReserved: true,
223
+ });
224
+ logger.info('Vercel webhook secret created');
225
+ }
226
+ } catch (error) {
227
+ logger.warn('Failed to store Vercel webhook secret', {
228
+ error: error instanceof Error ? error.message : String(error),
229
+ });
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Create a new deployment on Vercel
235
+ * POST /v13/deployments
236
+ */
237
+ async createDeployment(options: CreateDeploymentOptions = {}): Promise<VercelDeploymentResult> {
238
+ const credentials = await this.getCredentials();
239
+
240
+ try {
241
+ const response = await axios.post(
242
+ `https://api.vercel.com/v13/deployments?teamId=${credentials.teamId}&skipAutoDetectionConfirmation=1`,
243
+ {
244
+ name: options.name || 'deployment',
245
+ target: 'production',
246
+ project: credentials.projectId,
247
+ files: options.files,
248
+ projectSettings: options.projectSettings,
249
+ meta: options.meta,
250
+ },
251
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
252
+ );
253
+
254
+ const deployment = response.data;
255
+
256
+ logger.info('Vercel deployment created', {
257
+ id: deployment.id,
258
+ url: deployment.url,
259
+ readyState: deployment.readyState,
260
+ });
261
+
262
+ return {
263
+ id: deployment.id,
264
+ url: deployment.url ? `https://${deployment.url}` : null,
265
+ state: deployment.readyState,
266
+ readyState: deployment.readyState,
267
+ name: deployment.name,
268
+ createdAt: new Date(deployment.createdAt),
269
+ };
270
+ } catch (error) {
271
+ logger.error('Failed to create Vercel deployment', {
272
+ error: error instanceof Error ? error.message : String(error),
273
+ });
274
+ throw new AppError('Failed to create Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Get deployment status by deployment ID
280
+ * GET /v13/deployments/:id
281
+ */
282
+ async getDeployment(deploymentId: string): Promise<VercelDeploymentResult> {
283
+ const credentials = await this.getCredentials();
284
+
285
+ try {
286
+ const response = await axios.get(
287
+ `https://api.vercel.com/v13/deployments/${deploymentId}?teamId=${credentials.teamId}`,
288
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
289
+ );
290
+ const deployment = response.data;
291
+
292
+ return {
293
+ id: deployment.id,
294
+ url: deployment.url ? `https://${deployment.url}` : null,
295
+ state: deployment.readyState,
296
+ readyState: deployment.readyState,
297
+ name: deployment.name,
298
+ createdAt: new Date(deployment.createdAt),
299
+ error: deployment.errorCode
300
+ ? {
301
+ code: deployment.errorCode,
302
+ message: deployment.errorMessage || 'Unknown error',
303
+ }
304
+ : undefined,
305
+ };
306
+ } catch (error) {
307
+ if (axios.isAxiosError(error) && error.response?.status === 404) {
308
+ throw new AppError(`Deployment not found: ${deploymentId}`, 404, ERROR_CODES.NOT_FOUND);
309
+ }
310
+ logger.error('Failed to get Vercel deployment', {
311
+ error: error instanceof Error ? error.message : String(error),
312
+ deploymentId,
313
+ });
314
+ throw new AppError('Failed to get Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Cancel a deployment
320
+ * PATCH /v12/deployments/:id/cancel
321
+ */
322
+ async cancelDeployment(deploymentId: string): Promise<void> {
323
+ const credentials = await this.getCredentials();
324
+
325
+ try {
326
+ await axios.patch(
327
+ `https://api.vercel.com/v12/deployments/${deploymentId}/cancel?teamId=${credentials.teamId}`,
328
+ {},
329
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
330
+ );
331
+ logger.info('Vercel deployment cancelled', { deploymentId });
332
+ } catch (error) {
333
+ logger.error('Failed to cancel Vercel deployment', {
334
+ error: error instanceof Error ? error.message : String(error),
335
+ deploymentId,
336
+ });
337
+ throw new AppError('Failed to cancel Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Upsert environment variables for the project
343
+ * POST /v10/projects/:id/env
344
+ */
345
+ async upsertEnvironmentVariables(envVars: Array<{ key: string; value: string }>): Promise<void> {
346
+ const credentials = await this.getCredentials();
347
+
348
+ try {
349
+ const payload = envVars.map((env) => ({
350
+ key: env.key,
351
+ value: env.value,
352
+ type: 'encrypted',
353
+ target: ['production', 'preview', 'development'],
354
+ }));
355
+
356
+ await axios.post(
357
+ `https://api.vercel.com/v10/projects/${credentials.projectId}/env?teamId=${credentials.teamId}&upsert=true`,
358
+ payload,
359
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
360
+ );
361
+
362
+ logger.info('Environment variables upserted', {
363
+ count: envVars.length,
364
+ keys: envVars.map((e) => e.key),
365
+ });
366
+ } catch (error) {
367
+ logger.error('Failed to upsert environment variables', {
368
+ error: error instanceof Error ? error.message : String(error),
369
+ });
370
+ throw new AppError('Failed to upsert environment variables', 500, ERROR_CODES.INTERNAL_ERROR);
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Get all environment variable keys for the project
376
+ * GET /v9/projects/:id/env
377
+ */
378
+ async getEnvironmentVariableKeys(): Promise<string[]> {
379
+ const credentials = await this.getCredentials();
380
+
381
+ try {
382
+ const response = await axios.get(
383
+ `https://api.vercel.com/v9/projects/${credentials.projectId}/env?teamId=${credentials.teamId}`,
384
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
385
+ );
386
+
387
+ const data = response.data as { envs?: Array<{ key: string }> };
388
+ return (data.envs || []).map((env) => env.key);
389
+ } catch (error) {
390
+ logger.warn('Failed to get environment variable keys', {
391
+ error: error instanceof Error ? error.message : String(error),
392
+ });
393
+ return [];
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Clear cached credentials
399
+ */
400
+ clearCredentials(): void {
401
+ this.cloudCredentials = undefined;
402
+ this.fetchPromise = null;
403
+ logger.info('Vercel credentials cache cleared');
404
+ }
405
+
406
+ /**
407
+ * Upload a single file to Vercel
408
+ * POST /v2/files
409
+ */
410
+ async uploadFile(fileContent: Buffer): Promise<string> {
411
+ const credentials = await this.getCredentials();
412
+ const sha = this.computeSha(fileContent);
413
+
414
+ try {
415
+ await axios.post(
416
+ `https://api.vercel.com/v2/files?teamId=${credentials.teamId}`,
417
+ fileContent,
418
+ {
419
+ headers: {
420
+ Authorization: `Bearer ${credentials.token}`,
421
+ 'Content-Type': 'application/octet-stream',
422
+ 'Content-Length': fileContent.length.toString(),
423
+ 'x-vercel-digest': sha,
424
+ },
425
+ }
426
+ );
427
+
428
+ logger.info('File uploaded to Vercel', { sha, size: fileContent.length });
429
+ return sha;
430
+ } catch (error) {
431
+ // 409 Conflict means file already exists (same SHA), which is fine
432
+ if (axios.isAxiosError(error) && error.response?.status === 409) {
433
+ logger.info('File already exists on Vercel', { sha });
434
+ return sha;
435
+ }
436
+ logger.error('Failed to upload file to Vercel', {
437
+ error: error instanceof Error ? error.message : String(error),
438
+ });
439
+ throw new AppError('Failed to upload file to Vercel', 500, ERROR_CODES.INTERNAL_ERROR);
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Upload multiple files to Vercel in parallel
445
+ */
446
+ async uploadFiles(
447
+ files: Array<{ path: string; content: Buffer }>
448
+ ): Promise<Array<{ file: string; sha: string; size: number }>> {
449
+ const uploadPromises = files.map(async ({ path, content }) => {
450
+ const sha = await this.uploadFile(content);
451
+ return {
452
+ file: path,
453
+ sha,
454
+ size: content.length,
455
+ };
456
+ });
457
+
458
+ return Promise.all(uploadPromises);
459
+ }
460
+
461
+ /**
462
+ * Compute SHA-1 hash of file content
463
+ */
464
+ private computeSha(content: Buffer): string {
465
+ return crypto.createHash('sha1').update(content).digest('hex');
466
+ }
467
+
468
+ /**
469
+ * Create deployment using file SHAs (files must be pre-uploaded)
470
+ */
471
+ async createDeploymentWithFiles(
472
+ files: Array<{ file: string; sha: string; size: number }>,
473
+ options: Omit<CreateDeploymentOptions, 'files'> = {}
474
+ ): Promise<VercelDeploymentResult> {
475
+ const credentials = await this.getCredentials();
476
+
477
+ try {
478
+ const response = await axios.post(
479
+ `https://api.vercel.com/v13/deployments?teamId=${credentials.teamId}&skipAutoDetectionConfirmation=1`,
480
+ {
481
+ name: options.name || 'deployment',
482
+ target: 'production',
483
+ project: credentials.projectId,
484
+ files: files,
485
+ projectSettings: options.projectSettings,
486
+ meta: options.meta,
487
+ },
488
+ { headers: { Authorization: `Bearer ${credentials.token}` } }
489
+ );
490
+
491
+ const deployment = response.data;
492
+
493
+ logger.info('Vercel deployment created with file SHAs', {
494
+ id: deployment.id,
495
+ url: deployment.url,
496
+ readyState: deployment.readyState,
497
+ fileCount: files.length,
498
+ });
499
+
500
+ return {
501
+ id: deployment.id,
502
+ url: deployment.url ? `https://${deployment.url}` : null,
503
+ state: deployment.readyState,
504
+ readyState: deployment.readyState,
505
+ name: deployment.name,
506
+ createdAt: new Date(deployment.createdAt),
507
+ };
508
+ } catch (error) {
509
+ logger.error('Failed to create Vercel deployment with files', {
510
+ error: error instanceof Error ? error.message : String(error),
511
+ fileCount: files.length,
512
+ });
513
+ throw new AppError('Failed to create Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
514
+ }
515
+ }
516
+ }
@@ -1,4 +1,5 @@
1
1
  import { EmailTemplate } from '@/types/email.js';
2
+ import { SendRawEmailRequest } from '@insforge/shared-schemas';
2
3
 
3
4
  /**
4
5
  * Email provider interface
@@ -25,14 +26,10 @@ export interface EmailProvider {
25
26
  ): Promise<void>;
26
27
 
27
28
  /**
28
- * Send raw email with custom subject and body
29
- * Optional - not all providers may support this
30
- * @param to - Recipient email address
31
- * @param subject - Email subject
32
- * @param html - HTML email body
33
- * @param text - Plain text email body (optional)
29
+ * Send custom/raw email (optional - not all providers may support this)
30
+ * @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
34
31
  */
35
- sendRaw?(to: string, subject: string, html: string, text?: string): Promise<void>;
32
+ sendRaw?(options: SendRawEmailRequest): Promise<void>;
36
33
 
37
34
  /**
38
35
  * Check if provider supports template-based emails
@@ -5,6 +5,7 @@ import logger from '@/utils/logger.js';
5
5
  import { AppError } from '@/api/middlewares/error.js';
6
6
  import { ERROR_CODES } from '@/types/error-constants.js';
7
7
  import { EmailTemplate } from '@/types/email.js';
8
+ import { SendRawEmailRequest } from '@insforge/shared-schemas';
8
9
  import { EmailProvider } from './base.provider.js';
9
10
 
10
11
  /**
@@ -184,4 +185,87 @@ export class CloudEmailProvider implements EmailProvider {
184
185
  );
185
186
  }
186
187
  }
188
+
189
+ /**
190
+ * Send custom/raw email via cloud backend
191
+ */
192
+ async sendRaw(options: SendRawEmailRequest): Promise<void> {
193
+ try {
194
+ const projectId = config.cloud.projectId;
195
+ const apiHost = config.cloud.apiHost;
196
+ const signToken = this.generateSignToken();
197
+
198
+ const url = `${apiHost}/email/v1/${projectId}/send-on-demand`;
199
+ const response = await axios.post(url, options, {
200
+ headers: {
201
+ 'Content-Type': 'application/json',
202
+ sign: signToken,
203
+ },
204
+ timeout: 10000,
205
+ });
206
+
207
+ if (response.data?.success) {
208
+ logger.info('Raw email sent successfully', { projectId });
209
+ } else {
210
+ throw new AppError(
211
+ 'Email service returned unsuccessful response',
212
+ 500,
213
+ ERROR_CODES.INTERNAL_ERROR
214
+ );
215
+ }
216
+ } catch (error) {
217
+ if (axios.isAxiosError(error)) {
218
+ const status = error.response?.status;
219
+ const message = error.response?.data?.message || error.message;
220
+
221
+ logger.error('Failed to send raw email via cloud backend', {
222
+ projectId: config.cloud.projectId,
223
+ status,
224
+ message,
225
+ });
226
+
227
+ if (status === 401) {
228
+ throw new AppError(
229
+ 'Authentication failed with cloud email service.',
230
+ status,
231
+ ERROR_CODES.AUTH_UNAUTHORIZED
232
+ );
233
+ } else if (status === 403) {
234
+ throw new AppError(
235
+ 'Custom email service is not available for free plan. Please upgrade to use this feature.',
236
+ status,
237
+ ERROR_CODES.FORBIDDEN
238
+ );
239
+ } else if (status === 429) {
240
+ throw new AppError(
241
+ 'Email rate limit exceeded. Starter plan is limited 10 emails per hour, and Pro plan is limited 50 emails per hour',
242
+ status,
243
+ ERROR_CODES.RATE_LIMITED
244
+ );
245
+ } else if (status === 400) {
246
+ throw new AppError(
247
+ `Invalid email request: ${message}`,
248
+ status,
249
+ ERROR_CODES.INVALID_INPUT
250
+ );
251
+ } else {
252
+ throw new AppError(
253
+ `Failed to send email: ${message}`,
254
+ status || 500,
255
+ ERROR_CODES.INTERNAL_ERROR
256
+ );
257
+ }
258
+ }
259
+
260
+ if (error instanceof AppError) {
261
+ throw error;
262
+ }
263
+
264
+ throw new AppError(
265
+ `Failed to send email: ${error instanceof Error ? error.message : 'Unknown error'}`,
266
+ 500,
267
+ ERROR_CODES.INTERNAL_ERROR
268
+ );
269
+ }
270
+ }
187
271
  }