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,260 @@
1
+ import { Pool } from 'pg';
2
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
3
+ import logger from '@/utils/logger.js';
4
+ import type { RealtimeMessage, RoleSchema } from '@insforge/shared-schemas';
5
+ import { RealtimeChannelService } from './realtime-channel.service.js';
6
+ import { RealtimeAuthService } from './realtime-auth.service.js';
7
+
8
+ export class RealtimeMessageService {
9
+ private static instance: RealtimeMessageService;
10
+ private pool: Pool | null = null;
11
+
12
+ private constructor() {}
13
+
14
+ static getInstance(): RealtimeMessageService {
15
+ if (!RealtimeMessageService.instance) {
16
+ RealtimeMessageService.instance = new RealtimeMessageService();
17
+ }
18
+ return RealtimeMessageService.instance;
19
+ }
20
+
21
+ private getPool(): Pool {
22
+ if (!this.pool) {
23
+ this.pool = DatabaseManager.getInstance().getPool();
24
+ }
25
+ return this.pool;
26
+ }
27
+
28
+ /**
29
+ * Insert a message into the channel (client-initiated send).
30
+ * RLS INSERT policy controls who can send to which channels.
31
+ * pg_notify is automatically triggered by database trigger on insert.
32
+ *
33
+ * @returns The inserted message data for broadcasting, or null if RLS denied the insert
34
+ */
35
+ async insertMessage(
36
+ channelName: string,
37
+ eventName: string,
38
+ payload: Record<string, unknown>,
39
+ userId: string | undefined,
40
+ userRole: RoleSchema
41
+ ): Promise<{
42
+ channelId: string;
43
+ channelName: string;
44
+ eventName: string;
45
+ payload: Record<string, unknown>;
46
+ senderId: string | null;
47
+ } | null> {
48
+ // Get channel info
49
+ const channelService = RealtimeChannelService.getInstance();
50
+ const channel = await channelService.getByName(channelName);
51
+
52
+ if (!channel) {
53
+ logger.debug('Channel not found for message insert', { channelName });
54
+ return null;
55
+ }
56
+
57
+ const client = await this.getPool().connect();
58
+
59
+ try {
60
+ // Begin transaction to ensure settings persist across queries
61
+ await client.query('BEGIN');
62
+
63
+ // Switch to specified role to enforce RLS policies
64
+ await client.query(`SET LOCAL ROLE ${userRole}`);
65
+
66
+ // Set user context for RLS policy evaluation
67
+ const authService = RealtimeAuthService.getInstance();
68
+ await authService.setUserContext(client, userId, channelName);
69
+
70
+ // Attempt INSERT with sender info - RLS will allow/deny based on policies
71
+ // No RETURNING clause needed - trigger handles pg_notify
72
+ await client.query(
73
+ `INSERT INTO realtime.messages (event_name, channel_id, channel_name, payload, sender_type, sender_id)
74
+ VALUES ($1, $2, $3, $4, 'user', $5)`,
75
+ [eventName, channel.id, channelName, JSON.stringify(payload), userId || null]
76
+ );
77
+
78
+ // Commit transaction - insert succeeded
79
+ await client.query('COMMIT');
80
+
81
+ logger.debug('Client message inserted', {
82
+ channelName,
83
+ eventName,
84
+ userId,
85
+ });
86
+
87
+ return {
88
+ channelId: channel.id,
89
+ channelName,
90
+ eventName,
91
+ payload,
92
+ senderId: userId || null,
93
+ };
94
+ } catch (error) {
95
+ // Rollback transaction on error
96
+ await client.query('ROLLBACK').catch(() => {});
97
+
98
+ // RLS policy denied the INSERT or other error
99
+ logger.debug('Message insert denied or failed', { channelName, eventName, userId, error });
100
+ return null;
101
+ } finally {
102
+ // Reset role back to default before releasing connection
103
+ await client.query('RESET ROLE');
104
+ client.release();
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Get a message by ID (used by RealtimeManager after pg_notify)
110
+ */
111
+ async getById(id: string): Promise<RealtimeMessage | null> {
112
+ const result = await this.getPool().query(
113
+ `SELECT
114
+ id,
115
+ event_name as "eventName",
116
+ channel_id as "channelId",
117
+ channel_name as "channelName",
118
+ payload,
119
+ sender_type as "senderType",
120
+ sender_id as "senderId",
121
+ ws_audience_count as "wsAudienceCount",
122
+ wh_audience_count as "whAudienceCount",
123
+ wh_delivered_count as "whDeliveredCount",
124
+ created_at as "createdAt"
125
+ FROM realtime.messages
126
+ WHERE id = $1`,
127
+ [id]
128
+ );
129
+ return result.rows[0] || null;
130
+ }
131
+
132
+ async list(
133
+ options: {
134
+ channelId?: string;
135
+ eventName?: string;
136
+ limit?: number;
137
+ offset?: number;
138
+ } = {}
139
+ ): Promise<RealtimeMessage[]> {
140
+ const { channelId, eventName, limit = 100, offset = 0 } = options;
141
+
142
+ let query = `
143
+ SELECT
144
+ id,
145
+ event_name as "eventName",
146
+ channel_id as "channelId",
147
+ channel_name as "channelName",
148
+ payload,
149
+ sender_type as "senderType",
150
+ sender_id as "senderId",
151
+ ws_audience_count as "wsAudienceCount",
152
+ wh_audience_count as "whAudienceCount",
153
+ wh_delivered_count as "whDeliveredCount",
154
+ created_at as "createdAt"
155
+ FROM realtime.messages
156
+ WHERE 1=1
157
+ `;
158
+
159
+ const params: (string | number)[] = [];
160
+ let paramIndex = 1;
161
+
162
+ if (channelId) {
163
+ query += ` AND channel_id = $${paramIndex++}`;
164
+ params.push(channelId);
165
+ }
166
+
167
+ if (eventName) {
168
+ query += ` AND event_name = $${paramIndex++}`;
169
+ params.push(eventName);
170
+ }
171
+
172
+ query += ` ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`;
173
+ params.push(limit, offset);
174
+
175
+ const result = await this.getPool().query(query, params);
176
+ return result.rows;
177
+ }
178
+
179
+ /**
180
+ * Update message record with delivery statistics
181
+ */
182
+ async updateDeliveryStats(
183
+ messageId: string,
184
+ stats: {
185
+ wsAudienceCount: number;
186
+ whAudienceCount: number;
187
+ whDeliveredCount: number;
188
+ }
189
+ ): Promise<void> {
190
+ await this.getPool().query(
191
+ `UPDATE realtime.messages
192
+ SET
193
+ ws_audience_count = $2,
194
+ wh_audience_count = $3,
195
+ wh_delivered_count = $4
196
+ WHERE id = $1`,
197
+ [messageId, stats.wsAudienceCount, stats.whAudienceCount, stats.whDeliveredCount]
198
+ );
199
+ }
200
+
201
+ async getStats(
202
+ options: {
203
+ channelId?: string;
204
+ since?: Date;
205
+ } = {}
206
+ ): Promise<{
207
+ totalMessages: number;
208
+ whDeliveryRate: number;
209
+ topEvents: { eventName: string; count: number }[];
210
+ }> {
211
+ const { channelId, since } = options;
212
+
213
+ let whereClause = '1=1';
214
+ const params: (string | Date)[] = [];
215
+ let paramIndex = 1;
216
+
217
+ if (channelId) {
218
+ whereClause += ` AND channel_id = $${paramIndex++}`;
219
+ params.push(channelId);
220
+ }
221
+
222
+ if (since) {
223
+ whereClause += ` AND created_at >= $${paramIndex++}`;
224
+ params.push(since);
225
+ }
226
+
227
+ const statsResult = await this.getPool().query(
228
+ `SELECT
229
+ COUNT(*) as total_messages,
230
+ SUM(wh_audience_count) as wh_audience_total,
231
+ SUM(wh_delivered_count) as wh_delivered_total
232
+ FROM realtime.messages
233
+ WHERE ${whereClause}`,
234
+ params
235
+ );
236
+
237
+ const topEventsResult = await this.getPool().query(
238
+ `SELECT event_name, COUNT(*) as count
239
+ FROM realtime.messages
240
+ WHERE ${whereClause}
241
+ GROUP BY event_name
242
+ ORDER BY count DESC
243
+ LIMIT 10`,
244
+ params
245
+ );
246
+
247
+ const stats = statsResult.rows[0];
248
+ const whAudienceTotal = parseInt(stats.wh_audience_total) || 0;
249
+ const whDeliveredTotal = parseInt(stats.wh_delivered_total) || 0;
250
+
251
+ return {
252
+ totalMessages: parseInt(stats.total_messages) || 0,
253
+ whDeliveryRate: whAudienceTotal > 0 ? whDeliveredTotal / whAudienceTotal : 0,
254
+ topEvents: topEventsResult.rows.map((row) => ({
255
+ eventName: row.event_name,
256
+ count: parseInt(row.count),
257
+ })),
258
+ };
259
+ }
260
+ }
@@ -3,21 +3,9 @@ import crypto from 'crypto';
3
3
  import { DatabaseManager } from '@/infra/database/database.manager.js';
4
4
  import logger from '@/utils/logger.js';
5
5
  import { EncryptionManager } from '@/infra/security/encryption.manager.js';
6
+ import { SecretSchema, CreateSecretRequest } from '@insforge/shared-schemas';
6
7
 
7
- export interface SecretSchema {
8
- id: string;
9
- key: string;
10
- isActive: boolean;
11
- isReserved: boolean;
12
- lastUsedAt: Date | null;
13
- expiresAt: Date | null;
14
- createdAt: Date;
15
- updatedAt: Date;
16
- }
17
-
18
- export interface CreateSecretInput {
19
- key: string;
20
- value: string;
8
+ export interface CreateSecretInput extends CreateSecretRequest {
21
9
  isReserved?: boolean;
22
10
  expiresAt?: Date;
23
11
  }
@@ -59,7 +47,7 @@ export class SecretService {
59
47
  const encryptedValue = EncryptionManager.encrypt(input.value);
60
48
 
61
49
  const result = await this.getPool().query(
62
- `INSERT INTO _secrets (key, value_ciphertext, is_reserved, expires_at)
50
+ `INSERT INTO system.secrets (key, value_ciphertext, is_reserved, expires_at)
63
51
  VALUES ($1, $2, $3, $4)
64
52
  RETURNING id`,
65
53
  [input.key, encryptedValue, input.isReserved || false, input.expiresAt || null]
@@ -79,7 +67,7 @@ export class SecretService {
79
67
  async getSecretById(id: string): Promise<string | null> {
80
68
  try {
81
69
  const result = await this.getPool().query(
82
- `UPDATE _secrets
70
+ `UPDATE system.secrets
83
71
  SET last_used_at = NOW()
84
72
  WHERE id = $1 AND is_active = true
85
73
  AND (expires_at IS NULL OR expires_at > NOW())
@@ -106,7 +94,7 @@ export class SecretService {
106
94
  async getSecretByKey(key: string): Promise<string | null> {
107
95
  try {
108
96
  const result = await this.getPool().query(
109
- `UPDATE _secrets
97
+ `UPDATE system.secrets
110
98
  SET last_used_at = NOW()
111
99
  WHERE key = $1 AND is_active = true
112
100
  AND (expires_at IS NULL OR expires_at > NOW())
@@ -142,7 +130,7 @@ export class SecretService {
142
130
  expires_at as "expiresAt",
143
131
  created_at as "createdAt",
144
132
  updated_at as "updatedAt"
145
- FROM _secrets
133
+ FROM system.secrets
146
134
  ORDER BY created_at DESC`
147
135
  );
148
136
 
@@ -183,10 +171,14 @@ export class SecretService {
183
171
  values.push(input.expiresAt);
184
172
  }
185
173
 
174
+ if (updates.length === 0) {
175
+ return false;
176
+ }
177
+
186
178
  values.push(id);
187
179
 
188
180
  const result = await this.getPool().query(
189
- `UPDATE _secrets
181
+ `UPDATE system.secrets
190
182
  SET ${updates.join(', ')}
191
183
  WHERE id = $${paramCount}`,
192
184
  values
@@ -203,6 +195,81 @@ export class SecretService {
203
195
  }
204
196
  }
205
197
 
198
+ /**
199
+ * Update a secret by key
200
+ */
201
+ async updateSecretByKey(key: string, input: UpdateSecretInput): Promise<boolean> {
202
+ try {
203
+ const updates: string[] = [];
204
+ const values: (string | boolean | Date | null)[] = [];
205
+ let paramCount = 1;
206
+
207
+ if (input.value !== undefined) {
208
+ const encryptedValue = EncryptionManager.encrypt(input.value);
209
+ updates.push(`value_ciphertext = $${paramCount++}`);
210
+ values.push(encryptedValue);
211
+ }
212
+
213
+ if (input.isActive !== undefined) {
214
+ updates.push(`is_active = $${paramCount++}`);
215
+ values.push(input.isActive);
216
+ }
217
+
218
+ if (input.isReserved !== undefined) {
219
+ updates.push(`is_reserved = $${paramCount++}`);
220
+ values.push(input.isReserved);
221
+ }
222
+
223
+ if (input.expiresAt !== undefined) {
224
+ updates.push(`expires_at = $${paramCount++}`);
225
+ values.push(input.expiresAt);
226
+ }
227
+
228
+ if (updates.length === 0) {
229
+ return false;
230
+ }
231
+
232
+ values.push(key);
233
+
234
+ const result = await this.getPool().query(
235
+ `UPDATE system.secrets
236
+ SET ${updates.join(', ')}
237
+ WHERE key = $${paramCount}`,
238
+ values
239
+ );
240
+
241
+ const success = (result.rowCount ?? 0) > 0;
242
+ if (success) {
243
+ logger.info('Secret updated by key', { key });
244
+ }
245
+ return success;
246
+ } catch (error) {
247
+ logger.error('Failed to update secret by key', { error, key });
248
+ throw new Error('Failed to update secret');
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Delete a secret by key
254
+ */
255
+ async deleteSecretByKey(key: string): Promise<boolean> {
256
+ try {
257
+ const result = await this.getPool().query(
258
+ 'DELETE FROM system.secrets WHERE key = $1 AND is_reserved = false',
259
+ [key]
260
+ );
261
+
262
+ const success = (result.rowCount ?? 0) > 0;
263
+ if (success) {
264
+ logger.info('Secret deleted by key', { key });
265
+ }
266
+ return success;
267
+ } catch (error) {
268
+ logger.error('Failed to delete secret by key', { error, key });
269
+ throw new Error('Failed to delete secret');
270
+ }
271
+ }
272
+
206
273
  /**
207
274
  * Check if a secret value matches the stored value
208
275
  */
@@ -210,7 +277,7 @@ export class SecretService {
210
277
  try {
211
278
  // Optimized: Single query that retrieves and updates in one operation
212
279
  const result = await this.getPool().query(
213
- `UPDATE _secrets
280
+ `UPDATE system.secrets
214
281
  SET last_used_at = NOW()
215
282
  WHERE key = $1
216
283
  AND is_active = true
@@ -225,7 +292,12 @@ export class SecretService {
225
292
  }
226
293
 
227
294
  const decryptedValue = EncryptionManager.decrypt(result.rows[0].value_ciphertext);
228
- const matches = decryptedValue === value;
295
+ // Use constant-time comparison to prevent timing attacks
296
+ const decryptedBuffer = Buffer.from(decryptedValue);
297
+ const valueBuffer = Buffer.from(value);
298
+ const matches =
299
+ decryptedBuffer.length === valueBuffer.length &&
300
+ crypto.timingSafeEqual(decryptedBuffer, valueBuffer);
229
301
 
230
302
  if (matches) {
231
303
  logger.info('Secret check successful', { key });
@@ -247,7 +319,7 @@ export class SecretService {
247
319
  try {
248
320
  // Optimized: Single query with WHERE clause to prevent deleting reserved secrets
249
321
  const result = await this.getPool().query(
250
- 'DELETE FROM _secrets WHERE id = $1 AND is_reserved = false',
322
+ 'DELETE FROM system.secrets WHERE id = $1 AND is_reserved = false',
251
323
  [id]
252
324
  );
253
325
 
@@ -257,7 +329,7 @@ export class SecretService {
257
329
  } else {
258
330
  // Check if it exists but is reserved
259
331
  const checkResult = await this.getPool().query(
260
- 'SELECT is_reserved FROM _secrets WHERE id = $1',
332
+ 'SELECT is_reserved FROM system.secrets WHERE id = $1',
261
333
  [id]
262
334
  );
263
335
  if (checkResult.rows.length && checkResult.rows[0].is_reserved) {
@@ -279,7 +351,9 @@ export class SecretService {
279
351
  try {
280
352
  await client.query('BEGIN');
281
353
 
282
- const oldSecretResult = await client.query(`SELECT key FROM _secrets WHERE id = $1`, [id]);
354
+ const oldSecretResult = await client.query(`SELECT key FROM system.secrets WHERE id = $1`, [
355
+ id,
356
+ ]);
283
357
 
284
358
  if (!oldSecretResult.rows.length) {
285
359
  throw new Error('Secret not found');
@@ -288,7 +362,7 @@ export class SecretService {
288
362
  const secretKey = oldSecretResult.rows[0].key;
289
363
 
290
364
  await client.query(
291
- `UPDATE _secrets
365
+ `UPDATE system.secrets
292
366
  SET is_active = false,
293
367
  expires_at = NOW() + INTERVAL '24 hours'
294
368
  WHERE id = $1`,
@@ -297,7 +371,7 @@ export class SecretService {
297
371
 
298
372
  const encryptedValue = EncryptionManager.encrypt(newValue);
299
373
  const newSecretResult = await client.query(
300
- `INSERT INTO _secrets (key, value_ciphertext)
374
+ `INSERT INTO system.secrets (key, value_ciphertext)
301
375
  VALUES ($1, $2)
302
376
  RETURNING id`,
303
377
  [secretKey, encryptedValue]
@@ -327,7 +401,7 @@ export class SecretService {
327
401
  async cleanupExpiredSecrets(): Promise<number> {
328
402
  try {
329
403
  const result = await this.getPool().query(
330
- `DELETE FROM _secrets
404
+ `DELETE FROM system.secrets
331
405
  WHERE expires_at IS NOT NULL
332
406
  AND expires_at < NOW()
333
407
  RETURNING id`
@@ -109,7 +109,7 @@ export class StorageService {
109
109
  // This query finds all files matching the pattern and extracts the counter number
110
110
  const result = await this.getPool().query(
111
111
  `
112
- SELECT key FROM _storage
112
+ SELECT key FROM storage.objects
113
113
  WHERE bucket = $1
114
114
  AND (key = $2 OR key LIKE $3)
115
115
  `,
@@ -169,7 +169,7 @@ export class StorageService {
169
169
  // Save metadata to database and return the timestamp in one operation
170
170
  const result = await client.query(
171
171
  `
172
- INSERT INTO _storage (bucket, key, size, mime_type, uploaded_by)
172
+ INSERT INTO storage.objects (bucket, key, size, mime_type, uploaded_by)
173
173
  VALUES ($1, $2, $3, $4, $5)
174
174
  RETURNING uploaded_at as "uploadedAt"
175
175
  `,
@@ -207,7 +207,7 @@ export class StorageService {
207
207
  this.validateKey(key);
208
208
 
209
209
  const result = await this.getPool().query(
210
- 'SELECT * FROM _storage WHERE bucket = $1 AND key = $2',
210
+ 'SELECT * FROM storage.objects WHERE bucket = $1 AND key = $2',
211
211
  [bucket, key]
212
212
  );
213
213
 
@@ -249,7 +249,7 @@ export class StorageService {
249
249
  // Check permissions
250
250
  if (!isAdmin) {
251
251
  const fileResult = await client.query(
252
- 'SELECT uploaded_by FROM _storage WHERE bucket = $1 AND key = $2',
252
+ 'SELECT uploaded_by FROM storage.objects WHERE bucket = $1 AND key = $2',
253
253
  [bucket, key]
254
254
  );
255
255
 
@@ -273,10 +273,10 @@ export class StorageService {
273
273
  await this.provider.deleteObject(bucket, key);
274
274
 
275
275
  // Delete from database
276
- const result = await client.query('DELETE FROM _storage WHERE bucket = $1 AND key = $2', [
277
- bucket,
278
- key,
279
- ]);
276
+ const result = await client.query(
277
+ 'DELETE FROM storage.objects WHERE bucket = $1 AND key = $2',
278
+ [bucket, key]
279
+ );
280
280
 
281
281
  return result.rowCount !== null && result.rowCount > 0;
282
282
  } finally {
@@ -295,8 +295,8 @@ export class StorageService {
295
295
 
296
296
  const client = await this.getPool().connect();
297
297
  try {
298
- let query = 'SELECT * FROM _storage WHERE bucket = $1';
299
- let countQuery = 'SELECT COUNT(*) as count FROM _storage WHERE bucket = $1';
298
+ let query = 'SELECT * FROM storage.objects WHERE bucket = $1';
299
+ let countQuery = 'SELECT COUNT(*) as count FROM storage.objects WHERE bucket = $1';
300
300
  const params: (string | number)[] = [bucket];
301
301
  let paramIndex = 2;
302
302
 
@@ -338,7 +338,7 @@ export class StorageService {
338
338
 
339
339
  async isBucketPublic(bucket: string): Promise<boolean> {
340
340
  const result = await this.getPool().query(
341
- 'SELECT public FROM _storage_buckets WHERE name = $1',
341
+ 'SELECT public FROM storage.buckets WHERE name = $1',
342
342
  [bucket]
343
343
  );
344
344
  return result.rows[0]?.public || false;
@@ -348,7 +348,7 @@ export class StorageService {
348
348
  const client = await this.getPool().connect();
349
349
  try {
350
350
  // Check if bucket exists
351
- const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
351
+ const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
352
352
  bucket,
353
353
  ]);
354
354
 
@@ -356,9 +356,9 @@ export class StorageService {
356
356
  throw new Error(`Bucket "${bucket}" does not exist`);
357
357
  }
358
358
 
359
- // Update bucket visibility in _storage_buckets table
359
+ // Update bucket visibility in storage.buckets table
360
360
  await client.query(
361
- 'UPDATE _storage_buckets SET public = $1, updated_at = CURRENT_TIMESTAMP WHERE name = $2',
361
+ 'UPDATE storage.buckets SET public = $1, updated_at = CURRENT_TIMESTAMP WHERE name = $2',
362
362
  [isPublic, bucket]
363
363
  );
364
364
 
@@ -370,9 +370,9 @@ export class StorageService {
370
370
  }
371
371
 
372
372
  async listBuckets(): Promise<StorageBucketSchema[]> {
373
- // Get all buckets with their metadata from _storage_buckets table
373
+ // Get all buckets with their metadata from storage.buckets table
374
374
  const result = await this.getPool().query(
375
- 'SELECT name, public, created_at as "createdAt" FROM _storage_buckets ORDER BY name'
375
+ 'SELECT name, public, created_at as "createdAt" FROM storage.buckets ORDER BY name'
376
376
  );
377
377
 
378
378
  return result.rows as StorageBucketSchema[];
@@ -384,7 +384,7 @@ export class StorageService {
384
384
  const client = await this.getPool().connect();
385
385
  try {
386
386
  // Check if bucket already exists
387
- const existing = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
387
+ const existing = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
388
388
  bucket,
389
389
  ]);
390
390
 
@@ -392,8 +392,8 @@ export class StorageService {
392
392
  throw new Error(`Bucket "${bucket}" already exists`);
393
393
  }
394
394
 
395
- // Insert bucket into _storage_buckets table
396
- await client.query('INSERT INTO _storage_buckets (name, public) VALUES ($1, $2)', [
395
+ // Insert bucket into storage.buckets table
396
+ await client.query('INSERT INTO storage.buckets (name, public) VALUES ($1, $2)', [
397
397
  bucket,
398
398
  isPublic,
399
399
  ]);
@@ -414,7 +414,7 @@ export class StorageService {
414
414
  const client = await this.getPool().connect();
415
415
  try {
416
416
  // Check if bucket exists
417
- const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
417
+ const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
418
418
  bucket,
419
419
  ]);
420
420
 
@@ -425,8 +425,8 @@ export class StorageService {
425
425
  // Delete bucket using backend (handles all files)
426
426
  await this.provider.deleteBucket(bucket);
427
427
 
428
- // Delete from storage table (cascade will handle _storage entries)
429
- await client.query('DELETE FROM _storage_buckets WHERE name = $1', [bucket]);
428
+ // Delete from storage table (cascade will handle storage.objects entries)
429
+ await client.query('DELETE FROM storage.buckets WHERE name = $1', [bucket]);
430
430
 
431
431
  // Update storage metadata
432
432
  // Metadata is now updated on-demand
@@ -451,7 +451,7 @@ export class StorageService {
451
451
  const client = await this.getPool().connect();
452
452
  try {
453
453
  // Check if bucket exists
454
- const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
454
+ const bucketResult = await client.query('SELECT name FROM storage.buckets WHERE name = $1', [
455
455
  bucket,
456
456
  ]);
457
457
 
@@ -503,7 +503,7 @@ export class StorageService {
503
503
  try {
504
504
  // Check if already confirmed
505
505
  const existingResult = await client.query(
506
- 'SELECT key FROM _storage WHERE bucket = $1 AND key = $2',
506
+ 'SELECT key FROM storage.objects WHERE bucket = $1 AND key = $2',
507
507
  [bucket, key]
508
508
  );
509
509
 
@@ -514,7 +514,7 @@ export class StorageService {
514
514
  // Save metadata to database and return the timestamp in one operation
515
515
  const result = await client.query(
516
516
  `
517
- INSERT INTO _storage (bucket, key, size, mime_type, uploaded_by)
517
+ INSERT INTO storage.objects (bucket, key, size, mime_type, uploaded_by)
518
518
  VALUES ($1, $2, $3, $4, $5)
519
519
  RETURNING uploaded_at as "uploadedAt"
520
520
  `,
@@ -548,9 +548,9 @@ export class StorageService {
548
548
  * Get storage metadata
549
549
  */
550
550
  async getMetadata(): Promise<StorageMetadataSchema> {
551
- // Get storage buckets from _storage_buckets table
551
+ // Get storage buckets from storage.buckets table
552
552
  const result = await this.getPool().query(
553
- 'SELECT name, public, created_at as "createdAt" FROM _storage_buckets ORDER BY name'
553
+ 'SELECT name, public, created_at as "createdAt" FROM storage.buckets ORDER BY name'
554
554
  );
555
555
 
556
556
  const storageBuckets = result.rows as StorageBucketSchema[];
@@ -572,7 +572,7 @@ export class StorageService {
572
572
  try {
573
573
  // Query to get object count for each bucket
574
574
  const result = await this.getPool().query(
575
- 'SELECT bucket, COUNT(*) as count FROM _storage GROUP BY bucket'
575
+ 'SELECT bucket, COUNT(*) as count FROM storage.objects GROUP BY bucket'
576
576
  );
577
577
 
578
578
  const bucketCounts = result.rows as { bucket: string; count: string }[];
@@ -595,11 +595,11 @@ export class StorageService {
595
595
 
596
596
  private async getStorageSizeInGB(): Promise<number> {
597
597
  try {
598
- // Query the _storage table to sum all file sizes
598
+ // Query the storage.objects table to sum all file sizes
599
599
  const result = await this.getPool().query(
600
600
  `
601
601
  SELECT COALESCE(SUM(size), 0) as total_size
602
- FROM _storage
602
+ FROM storage.objects
603
603
  `
604
604
  );
605
605