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
@@ -1,6 +1,7 @@
1
1
  import { EmailProvider } from '@/providers/email/base.provider.js';
2
2
  import { CloudEmailProvider } from '@/providers/email/cloud.provider.js';
3
3
  import { EmailTemplate } from '@/types/email.js';
4
+ import { SendRawEmailRequest } from '@insforge/shared-schemas';
4
5
  import logger from '@/utils/logger.js';
5
6
 
6
7
  /**
@@ -53,17 +54,14 @@ export class EmailService {
53
54
  }
54
55
 
55
56
  /**
56
- * Send raw email (if provider supports it)
57
- * @param to - Recipient email address
58
- * @param subject - Email subject
59
- * @param html - HTML email body
60
- * @param text - Plain text email body (optional)
57
+ * Send custom/raw email
58
+ * @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
61
59
  */
62
- public async sendRaw(to: string, subject: string, html: string, text?: string): Promise<void> {
60
+ public async sendRaw(options: SendRawEmailRequest): Promise<void> {
63
61
  if (!this.provider.sendRaw) {
64
62
  throw new Error('Current email provider does not support raw email sending');
65
63
  }
66
- return this.provider.sendRaw(to, subject, html, text);
64
+ return this.provider.sendRaw(options);
67
65
  }
68
66
 
69
67
  /**
@@ -1,8 +1,10 @@
1
1
  import { DatabaseManager } from '@/infra/database/database.manager.js';
2
2
  import {
3
3
  EdgeFunctionMetadataSchema,
4
- FunctionUploadRequest,
5
- FunctionUpdateRequest,
4
+ UploadFunctionRequest,
5
+ UpdateFunctionRequest,
6
+ FunctionSchema,
7
+ ListFunctionsResponse,
6
8
  } from '@insforge/shared-schemas';
7
9
  import logger from '@/utils/logger.js';
8
10
  import { DatabaseError, Pool } from 'pg';
@@ -10,13 +12,6 @@ import fetch from 'node-fetch';
10
12
  import { AppError } from '@/api/middlewares/error.js';
11
13
  import { ERROR_CODES } from '@/types/error-constants.js';
12
14
 
13
- export interface FunctionWithRuntime {
14
- functions: Record<string, unknown>[];
15
- runtime: {
16
- status: 'running' | 'unavailable';
17
- };
18
- }
19
-
20
15
  export class FunctionService {
21
16
  private static instance: FunctionService;
22
17
  private pool: Pool | null = null;
@@ -41,13 +36,19 @@ export class FunctionService {
41
36
  /**
42
37
  * List all functions with runtime health check
43
38
  */
44
- async listFunctions(): Promise<FunctionWithRuntime> {
39
+ async listFunctions(): Promise<ListFunctionsResponse> {
45
40
  try {
46
41
  const result = await this.getPool().query(
47
42
  `SELECT
48
- id, slug, name, description, status,
49
- created_at, updated_at, deployed_at
50
- FROM _functions
43
+ id,
44
+ slug,
45
+ name,
46
+ description,
47
+ status,
48
+ created_at as "createdAt",
49
+ updated_at as "updatedAt",
50
+ deployed_at as "deployedAt"
51
+ FROM functions.definitions
51
52
  ORDER BY created_at DESC`
52
53
  );
53
54
 
@@ -86,13 +87,20 @@ export class FunctionService {
86
87
  /**
87
88
  * Get a specific function by slug
88
89
  */
89
- async getFunction(slug: string): Promise<Record<string, unknown> | undefined> {
90
+ async getFunction(slug: string): Promise<FunctionSchema | undefined> {
90
91
  try {
91
92
  const result = await this.getPool().query(
92
93
  `SELECT
93
- id, slug, name, description, code, status,
94
- created_at, updated_at, deployed_at
95
- FROM _functions
94
+ id,
95
+ slug,
96
+ name,
97
+ description,
98
+ code,
99
+ status,
100
+ created_at as "createdAt",
101
+ updated_at as "updatedAt",
102
+ deployed_at as "deployedAt"
103
+ FROM functions.definitions
96
104
  WHERE slug = $1`,
97
105
  [slug]
98
106
  );
@@ -111,7 +119,7 @@ export class FunctionService {
111
119
  /**
112
120
  * Create a new function
113
121
  */
114
- async createFunction(data: FunctionUploadRequest): Promise<Record<string, unknown>> {
122
+ async createFunction(data: UploadFunctionRequest): Promise<FunctionSchema> {
115
123
  const client = await this.getPool().connect();
116
124
  try {
117
125
  const { name, code, description, status } = data;
@@ -125,22 +133,23 @@ export class FunctionService {
125
133
 
126
134
  // Insert function
127
135
  await client.query(
128
- `INSERT INTO _functions (id, slug, name, description, code, status)
136
+ `INSERT INTO functions.definitions (id, slug, name, description, code, status)
129
137
  VALUES ($1, $2, $3, $4, $5, $6)`,
130
138
  [id, slug, name, description || null, code, status]
131
139
  );
132
140
 
133
141
  // If status is active, update deployed_at
134
142
  if (status === 'active') {
135
- await client.query(`UPDATE _functions SET deployed_at = CURRENT_TIMESTAMP WHERE id = $1`, [
136
- id,
137
- ]);
143
+ await client.query(
144
+ `UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE id = $1`,
145
+ [id]
146
+ );
138
147
  }
139
148
 
140
149
  // Fetch the created function
141
150
  const result = await client.query(
142
- `SELECT id, slug, name, description, status, created_at
143
- FROM _functions WHERE id = $1`,
151
+ `SELECT id, slug, name, description, status, created_at as "createdAt"
152
+ FROM functions.definitions WHERE id = $1`,
144
153
  [id]
145
154
  );
146
155
 
@@ -176,14 +185,15 @@ export class FunctionService {
176
185
  */
177
186
  async updateFunction(
178
187
  slug: string,
179
- updates: FunctionUpdateRequest
180
- ): Promise<Record<string, unknown> | null> {
188
+ updates: UpdateFunctionRequest
189
+ ): Promise<FunctionSchema | null> {
181
190
  const client = await this.getPool().connect();
182
191
  try {
183
192
  // Check if function exists
184
- const existingResult = await client.query('SELECT id FROM _functions WHERE slug = $1', [
185
- slug,
186
- ]);
193
+ const existingResult = await client.query(
194
+ 'SELECT id FROM functions.definitions WHERE slug = $1',
195
+ [slug]
196
+ );
187
197
  if (existingResult.rows.length === 0) {
188
198
  return null;
189
199
  }
@@ -195,22 +205,28 @@ export class FunctionService {
195
205
 
196
206
  // Update fields
197
207
  if (updates.name !== undefined) {
198
- await client.query('UPDATE _functions SET name = $1 WHERE slug = $2', [updates.name, slug]);
208
+ await client.query('UPDATE functions.definitions SET name = $1 WHERE slug = $2', [
209
+ updates.name,
210
+ slug,
211
+ ]);
199
212
  }
200
213
 
201
214
  if (updates.description !== undefined) {
202
- await client.query('UPDATE _functions SET description = $1 WHERE slug = $2', [
215
+ await client.query('UPDATE functions.definitions SET description = $1 WHERE slug = $2', [
203
216
  updates.description,
204
217
  slug,
205
218
  ]);
206
219
  }
207
220
 
208
221
  if (updates.code !== undefined) {
209
- await client.query('UPDATE _functions SET code = $1 WHERE slug = $2', [updates.code, slug]);
222
+ await client.query('UPDATE functions.definitions SET code = $1 WHERE slug = $2', [
223
+ updates.code,
224
+ slug,
225
+ ]);
210
226
  }
211
227
 
212
228
  if (updates.status !== undefined) {
213
- await client.query('UPDATE _functions SET status = $1 WHERE slug = $2', [
229
+ await client.query('UPDATE functions.definitions SET status = $1 WHERE slug = $2', [
214
230
  updates.status,
215
231
  slug,
216
232
  ]);
@@ -218,21 +234,22 @@ export class FunctionService {
218
234
  // Update deployed_at if status changes to active
219
235
  if (updates.status === 'active') {
220
236
  await client.query(
221
- 'UPDATE _functions SET deployed_at = CURRENT_TIMESTAMP WHERE slug = $1',
237
+ 'UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE slug = $1',
222
238
  [slug]
223
239
  );
224
240
  }
225
241
  }
226
242
 
227
243
  // Update updated_at
228
- await client.query('UPDATE _functions SET updated_at = CURRENT_TIMESTAMP WHERE slug = $1', [
229
- slug,
230
- ]);
244
+ await client.query(
245
+ 'UPDATE functions.definitions SET updated_at = CURRENT_TIMESTAMP WHERE slug = $1',
246
+ [slug]
247
+ );
231
248
 
232
249
  // Fetch updated function
233
250
  const result = await client.query(
234
- `SELECT id, slug, name, description, status, updated_at
235
- FROM _functions WHERE slug = $1`,
251
+ `SELECT id, slug, name, description, status, updated_at as "updatedAt", deployed_at as "deployedAt"
252
+ FROM functions.definitions WHERE slug = $1`,
236
253
  [slug]
237
254
  );
238
255
 
@@ -254,7 +271,10 @@ export class FunctionService {
254
271
  */
255
272
  async deleteFunction(slug: string): Promise<boolean> {
256
273
  try {
257
- const result = await this.getPool().query('DELETE FROM _functions WHERE slug = $1', [slug]);
274
+ const result = await this.getPool().query(
275
+ 'DELETE FROM functions.definitions WHERE slug = $1',
276
+ [slug]
277
+ );
258
278
 
259
279
  if (result.rowCount === 0) {
260
280
  return false;
@@ -278,7 +298,7 @@ export class FunctionService {
278
298
  try {
279
299
  const result = await this.getPool().query(
280
300
  `SELECT slug, name, description, status
281
- FROM _functions
301
+ FROM functions.definitions
282
302
  ORDER BY created_at DESC`
283
303
  );
284
304
 
@@ -35,7 +35,7 @@ export class AuditService {
35
35
  try {
36
36
  const pool = this.getPool();
37
37
  const result = await pool.query(
38
- `INSERT INTO _audit_logs (actor, action, module, details, ip_address)
38
+ `INSERT INTO system.audit_logs (actor, action, module, details, ip_address)
39
39
  VALUES ($1, $2, $3, $4, $5)
40
40
  RETURNING *`,
41
41
  [
@@ -109,12 +109,12 @@ export class AuditService {
109
109
  }
110
110
 
111
111
  // Get total count first
112
- const countSql = `SELECT COUNT(*) as count FROM _audit_logs ${whereClause}`;
112
+ const countSql = `SELECT COUNT(*) as count FROM system.audit_logs ${whereClause}`;
113
113
  const countResult = await pool.query(countSql, params);
114
114
  const total = parseInt(countResult.rows[0].count, 10);
115
115
 
116
116
  // Get paginated records
117
- let dataSql = `SELECT * FROM _audit_logs ${whereClause} ORDER BY created_at DESC`;
117
+ let dataSql = `SELECT * FROM system.audit_logs ${whereClause} ORDER BY created_at DESC`;
118
118
  const dataParams = [...params];
119
119
 
120
120
  if (query.limit) {
@@ -154,7 +154,7 @@ export class AuditService {
154
154
  async getById(id: string): Promise<AuditLogSchema | null> {
155
155
  try {
156
156
  const pool = this.getPool();
157
- const result = await pool.query('SELECT * FROM _audit_logs WHERE id = $1', [id]);
157
+ const result = await pool.query('SELECT * FROM system.audit_logs WHERE id = $1', [id]);
158
158
 
159
159
  const row = result.rows[0];
160
160
 
@@ -186,30 +186,30 @@ export class AuditService {
186
186
  startDate.setDate(startDate.getDate() - days);
187
187
 
188
188
  const totalLogsResult = await pool.query(
189
- 'SELECT COUNT(*) as count FROM _audit_logs WHERE created_at >= $1',
189
+ 'SELECT COUNT(*) as count FROM system.audit_logs WHERE created_at >= $1',
190
190
  [startDate.toISOString()]
191
191
  );
192
192
 
193
193
  const uniqueActorsResult = await pool.query(
194
- 'SELECT COUNT(DISTINCT actor) as count FROM _audit_logs WHERE created_at >= $1',
194
+ 'SELECT COUNT(DISTINCT actor) as count FROM system.audit_logs WHERE created_at >= $1',
195
195
  [startDate.toISOString()]
196
196
  );
197
197
 
198
198
  const uniqueModulesResult = await pool.query(
199
- 'SELECT COUNT(DISTINCT module) as count FROM _audit_logs WHERE created_at >= $1',
199
+ 'SELECT COUNT(DISTINCT module) as count FROM system.audit_logs WHERE created_at >= $1',
200
200
  [startDate.toISOString()]
201
201
  );
202
202
 
203
203
  const actionsByModuleResult = await pool.query(
204
204
  `SELECT module, COUNT(*) as count
205
- FROM _audit_logs
205
+ FROM system.audit_logs
206
206
  WHERE created_at >= $1
207
207
  GROUP BY module`,
208
208
  [startDate.toISOString()]
209
209
  );
210
210
 
211
211
  const recentActivityResult = await pool.query(
212
- `SELECT * FROM _audit_logs
212
+ `SELECT * FROM system.audit_logs
213
213
  WHERE created_at >= $1
214
214
  ORDER BY created_at DESC
215
215
  LIMIT 10`,
@@ -253,7 +253,7 @@ export class AuditService {
253
253
  cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
254
254
 
255
255
  const result = await pool.query(
256
- 'DELETE FROM _audit_logs WHERE created_at < $1 RETURNING id',
256
+ 'DELETE FROM system.audit_logs WHERE created_at < $1 RETURNING id',
257
257
  [cutoffDate.toISOString()]
258
258
  );
259
259
 
@@ -0,0 +1,3 @@
1
+ export { RealtimeChannelService } from './realtime-channel.service.js';
2
+ export { RealtimeAuthService } from './realtime-auth.service.js';
3
+ export { RealtimeMessageService } from './realtime-message.service.js';
@@ -0,0 +1,104 @@
1
+ import { Pool, PoolClient } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import logger from '@/utils/logger.js';
4
+ import { RoleSchema } from '@insforge/shared-schemas';
5
+
6
+ /**
7
+ * Handles channel authorization by checking RLS policies on the messages table.
8
+ *
9
+ * Permission Model (Supabase pattern):
10
+ * - SELECT on messages = 'join' permission (can subscribe to channel)
11
+ * - INSERT on messages = 'send' permission (can publish to channel)
12
+ *
13
+ * Developers define RLS policies on realtime.messages that check:
14
+ * - current_setting('request.jwt.claim.sub', true) = user ID
15
+ * - current_setting('request.jwt.claim.role', true) = user role
16
+ * - channel_name for channel-specific access
17
+ */
18
+ export class RealtimeAuthService {
19
+ private static instance: RealtimeAuthService;
20
+ private pool: Pool | null = null;
21
+
22
+ private constructor() {}
23
+
24
+ static getInstance(): RealtimeAuthService {
25
+ if (!RealtimeAuthService.instance) {
26
+ RealtimeAuthService.instance = new RealtimeAuthService();
27
+ }
28
+ return RealtimeAuthService.instance;
29
+ }
30
+
31
+ private getPool(): Pool {
32
+ if (!this.pool) {
33
+ this.pool = DatabaseManager.getInstance().getPool();
34
+ }
35
+ return this.pool;
36
+ }
37
+
38
+ /**
39
+ * Check if user has permission to subscribe to a channel.
40
+ * Tests SELECT permission on channels table via RLS.
41
+ *
42
+ * @param channelName - The channel to check access for
43
+ * @param userId - The user ID (undefined for anonymous users)
44
+ * @param role - The database role to use (authenticated or anon)
45
+ * @returns true if user can subscribe, false otherwise
46
+ */
47
+ async checkSubscribePermission(
48
+ channelName: string,
49
+ userId: string | undefined,
50
+ role: RoleSchema
51
+ ): Promise<boolean> {
52
+ const client = await this.getPool().connect();
53
+ try {
54
+ // Begin transaction to ensure settings persist across queries
55
+ await client.query('BEGIN');
56
+ // Switch to specified role to enforce RLS policies
57
+ await client.query(`SET LOCAL ROLE ${role}`);
58
+ await this.setUserContext(client, userId, channelName);
59
+
60
+ // Test SELECT permission via RLS on channels table
61
+ const result = await client.query(
62
+ `SELECT 1 FROM realtime.channels
63
+ WHERE enabled = TRUE
64
+ AND (pattern = $1 OR $1 LIKE pattern)
65
+ LIMIT 1`,
66
+ [channelName]
67
+ );
68
+
69
+ // Commit transaction
70
+ await client.query('COMMIT');
71
+
72
+ // If query returns a row, user has permission
73
+ return result.rowCount !== null && result.rowCount > 0;
74
+ } catch (error) {
75
+ // Rollback transaction on error
76
+ await client.query('ROLLBACK').catch(() => {});
77
+ logger.debug('Subscribe permission denied', { channelName, userId, error });
78
+ return false;
79
+ } finally {
80
+ // Reset role back to default before releasing connection
81
+ await client.query('RESET ROLE');
82
+ client.release();
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Set user context variables for RLS policy evaluation.
88
+ * Can be used by other services that need to execute queries with user context.
89
+ */
90
+ async setUserContext(
91
+ client: PoolClient,
92
+ userId: string | undefined,
93
+ channelName: string
94
+ ): Promise<void> {
95
+ if (userId) {
96
+ await client.query("SELECT set_config('request.jwt.claim.sub', $1, true)", [userId]);
97
+ } else {
98
+ await client.query("SELECT set_config('request.jwt.claim.sub', '', true)");
99
+ }
100
+
101
+ // Set the channel being accessed (used by realtime.channel_name())
102
+ await client.query("SELECT set_config('realtime.channel_name', $1, true)", [channelName]);
103
+ }
104
+ }
@@ -0,0 +1,237 @@
1
+ import { Pool } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import { AppError } from '@/api/middlewares/error.js';
4
+ import { ERROR_CODES } from '@/types/error-constants.js';
5
+ import logger from '@/utils/logger.js';
6
+ import type {
7
+ RealtimeChannel,
8
+ CreateChannelRequest,
9
+ UpdateChannelRequest,
10
+ RealtimeMetadataSchema,
11
+ RlsPolicy,
12
+ RealtimePermissionsResponse,
13
+ } from '@insforge/shared-schemas';
14
+
15
+ const SYSTEM_POLICIES = ['project_admin_policy'];
16
+
17
+ export class RealtimeChannelService {
18
+ private static instance: RealtimeChannelService;
19
+ private pool: Pool | null = null;
20
+
21
+ private constructor() {}
22
+
23
+ static getInstance(): RealtimeChannelService {
24
+ if (!RealtimeChannelService.instance) {
25
+ RealtimeChannelService.instance = new RealtimeChannelService();
26
+ }
27
+ return RealtimeChannelService.instance;
28
+ }
29
+
30
+ private getPool(): Pool {
31
+ if (!this.pool) {
32
+ this.pool = DatabaseManager.getInstance().getPool();
33
+ }
34
+ return this.pool;
35
+ }
36
+
37
+ async list(): Promise<RealtimeChannel[]> {
38
+ const result = await this.getPool().query(`
39
+ SELECT
40
+ id,
41
+ pattern,
42
+ description,
43
+ webhook_urls as "webhookUrls",
44
+ enabled,
45
+ created_at as "createdAt",
46
+ updated_at as "updatedAt"
47
+ FROM realtime.channels
48
+ ORDER BY created_at DESC
49
+ `);
50
+ return result.rows;
51
+ }
52
+
53
+ async getById(id: string): Promise<RealtimeChannel | null> {
54
+ const result = await this.getPool().query(
55
+ `SELECT
56
+ id,
57
+ pattern,
58
+ description,
59
+ webhook_urls as "webhookUrls",
60
+ enabled,
61
+ created_at as "createdAt",
62
+ updated_at as "updatedAt"
63
+ FROM realtime.channels
64
+ WHERE id = $1`,
65
+ [id]
66
+ );
67
+ return result.rows[0] || null;
68
+ }
69
+
70
+ /**
71
+ * Find a channel by name (exact match or wildcard pattern match).
72
+ * For wildcard patterns like "order:%", checks if channelName matches the pattern.
73
+ * Returns the matching channel if found and enabled, null otherwise.
74
+ */
75
+ async getByName(channelName: string): Promise<RealtimeChannel | null> {
76
+ const result = await this.getPool().query(
77
+ `SELECT
78
+ id,
79
+ pattern,
80
+ description,
81
+ webhook_urls as "webhookUrls",
82
+ enabled,
83
+ created_at as "createdAt",
84
+ updated_at as "updatedAt"
85
+ FROM realtime.channels
86
+ WHERE enabled = TRUE
87
+ AND (pattern = $1 OR $1 LIKE pattern)
88
+ ORDER BY pattern = $1 DESC
89
+ LIMIT 1`,
90
+ [channelName]
91
+ );
92
+ return result.rows[0] || null;
93
+ }
94
+
95
+ async create(input: CreateChannelRequest): Promise<RealtimeChannel> {
96
+ this.validateChannelPattern(input.pattern);
97
+
98
+ const result = await this.getPool().query(
99
+ `INSERT INTO realtime.channels (
100
+ pattern, description, webhook_urls, enabled
101
+ ) VALUES ($1, $2, $3, $4)
102
+ RETURNING
103
+ id,
104
+ pattern,
105
+ description,
106
+ webhook_urls as "webhookUrls",
107
+ enabled,
108
+ created_at as "createdAt",
109
+ updated_at as "updatedAt"`,
110
+ [input.pattern, input.description || null, input.webhookUrls || null, input.enabled ?? true]
111
+ );
112
+
113
+ logger.info('Realtime channel created', { pattern: input.pattern });
114
+ return result.rows[0];
115
+ }
116
+
117
+ async update(id: string, input: UpdateChannelRequest): Promise<RealtimeChannel> {
118
+ const existing = await this.getById(id);
119
+ if (!existing) {
120
+ throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
121
+ }
122
+
123
+ if (input.pattern) {
124
+ this.validateChannelPattern(input.pattern);
125
+ }
126
+
127
+ const result = await this.getPool().query(
128
+ `UPDATE realtime.channels
129
+ SET
130
+ pattern = COALESCE($2, pattern),
131
+ description = COALESCE($3, description),
132
+ webhook_urls = COALESCE($4, webhook_urls),
133
+ enabled = COALESCE($5, enabled)
134
+ WHERE id = $1
135
+ RETURNING
136
+ id,
137
+ pattern,
138
+ description,
139
+ webhook_urls as "webhookUrls",
140
+ enabled,
141
+ created_at as "createdAt",
142
+ updated_at as "updatedAt"`,
143
+ [id, input.pattern, input.description, input.webhookUrls, input.enabled]
144
+ );
145
+
146
+ logger.info('Realtime channel updated', { id });
147
+ return result.rows[0];
148
+ }
149
+
150
+ async delete(id: string): Promise<void> {
151
+ const existing = await this.getById(id);
152
+ if (!existing) {
153
+ throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
154
+ }
155
+
156
+ await this.getPool().query('DELETE FROM realtime.channels WHERE id = $1', [id]);
157
+ logger.info('Realtime channel deleted', { id, pattern: existing.pattern });
158
+ }
159
+
160
+ /**
161
+ * Get realtime metadata including channels and permissions
162
+ */
163
+ async getMetadata(): Promise<RealtimeMetadataSchema> {
164
+ const [channels, permissions] = await Promise.all([this.list(), this.getPermissions()]);
165
+
166
+ return {
167
+ channels,
168
+ permissions,
169
+ };
170
+ }
171
+
172
+ // ============================================================================
173
+ // Permissions Methods
174
+ // ============================================================================
175
+
176
+ /**
177
+ * Get RLS policies for a table in the realtime schema, excluding system policies
178
+ */
179
+ private async getPolicies(tableName: string): Promise<RlsPolicy[]> {
180
+ const result = await this.getPool().query(
181
+ `SELECT
182
+ policyname as "policyName",
183
+ tablename as "tableName",
184
+ cmd as "command",
185
+ roles,
186
+ qual as "using",
187
+ with_check as "withCheck"
188
+ FROM pg_policies
189
+ WHERE schemaname = 'realtime'
190
+ AND tablename = $1
191
+ ORDER BY policyname`,
192
+ [tableName]
193
+ );
194
+
195
+ // Filter out system policies
196
+ return result.rows.filter((policy) => !SYSTEM_POLICIES.includes(policy.policyName));
197
+ }
198
+
199
+ /**
200
+ * Get all realtime permissions (RLS policies for channels and messages tables)
201
+ *
202
+ * - Subscribe permission: RLS policies on realtime.channels (SELECT)
203
+ * - Publish permission: RLS policies on realtime.messages (INSERT)
204
+ */
205
+ async getPermissions(): Promise<RealtimePermissionsResponse> {
206
+ const [channelsPolicies, messagesPolicies] = await Promise.all([
207
+ this.getPolicies('channels'),
208
+ this.getPolicies('messages'),
209
+ ]);
210
+
211
+ return {
212
+ subscribe: {
213
+ policies: channelsPolicies,
214
+ },
215
+ publish: {
216
+ policies: messagesPolicies,
217
+ },
218
+ };
219
+ }
220
+
221
+ // ============================================================================
222
+ // Validation
223
+ // ============================================================================
224
+
225
+ private validateChannelPattern(pattern: string): void {
226
+ // Allow alphanumeric, colons, hyphens, and % for wildcards
227
+ // Note: underscore is not allowed as it's a SQL wildcard character
228
+ const validPattern = /^[a-zA-Z0-9-]+(:[a-zA-Z0-9%:-]+)*$/;
229
+ if (!validPattern.test(pattern)) {
230
+ throw new AppError(
231
+ 'Invalid channel pattern. Use alphanumeric characters, colons, hyphens, and % for wildcards.',
232
+ 400,
233
+ ERROR_CODES.INVALID_INPUT
234
+ );
235
+ }
236
+ }
237
+ }