insforge 0.3.3 → 1.3.0

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 (635) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.dockerignore +60 -57
  3. package/.env.example +84 -49
  4. package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -83
  5. package/.github/ISSUE_TEMPLATE/config.yml +11 -11
  6. package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -79
  7. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  8. package/.github/copilot-instructions.md +146 -146
  9. package/.github/workflows/build-image.yml +66 -65
  10. package/.github/workflows/ci-premerge-check.yml +23 -23
  11. package/.github/workflows/e2e.yml +63 -0
  12. package/.github/workflows/lint-and-format.yml +32 -32
  13. package/.prettierignore +64 -64
  14. package/CHANGELOG.md +44 -3
  15. package/CLAUDE_PLUGIN.md +104 -0
  16. package/CODE_OF_CONDUCT.md +128 -0
  17. package/CONTRIBUTING.md +125 -125
  18. package/Dockerfile +30 -27
  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 -134
  23. package/assets/Dark.svg +23 -23
  24. package/assets/mcpInstallv2.png +0 -0
  25. package/assets/sampleResponse.png +0 -0
  26. package/auth/index.html +13 -0
  27. package/auth/package.json +28 -0
  28. package/auth/public/favicon.ico +0 -0
  29. package/auth/src/App.tsx +33 -0
  30. package/auth/src/components/ErrorCard.tsx +37 -0
  31. package/auth/src/components/Layout.tsx +13 -0
  32. package/auth/src/index.css +19 -0
  33. package/auth/src/lib/broadcastService.ts +117 -0
  34. package/auth/src/lib/utils.ts +11 -0
  35. package/auth/src/main.tsx +22 -0
  36. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  37. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  38. package/auth/src/pages/SignInPage.tsx +60 -0
  39. package/auth/src/pages/SignUpPage.tsx +60 -0
  40. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  41. package/auth/src/vite-env.d.ts +10 -0
  42. package/auth/tsconfig.json +32 -0
  43. package/auth/tsconfig.node.json +11 -0
  44. package/auth/vite.config.ts +25 -0
  45. package/backend/package.json +78 -75
  46. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  47. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  48. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +22 -26
  49. package/backend/src/api/routes/auth/index.routes.ts +667 -0
  50. package/backend/src/api/routes/auth/oauth.routes.ts +473 -0
  51. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +128 -65
  52. package/backend/src/api/routes/database/index.routes.ts +90 -0
  53. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +26 -12
  54. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +6 -23
  55. package/backend/src/api/routes/docs/index.routes.ts +75 -0
  56. package/backend/src/api/routes/email/index.routes.ts +35 -0
  57. package/backend/src/api/routes/functions/index.routes.ts +194 -0
  58. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  59. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +33 -31
  60. package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
  61. package/backend/src/api/routes/realtime/index.routes.ts +12 -0
  62. package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
  63. package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
  64. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  65. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +48 -61
  66. package/backend/src/api/routes/usage/index.routes.ts +91 -0
  67. package/backend/src/infra/config/app.config.ts +51 -0
  68. package/backend/src/infra/database/database.manager.ts +182 -0
  69. package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +141 -141
  70. package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +40 -40
  71. package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +29 -29
  72. package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +55 -55
  73. package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +23 -23
  74. package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +29 -29
  75. package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +24 -24
  76. package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +1 -1
  77. package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +76 -76
  78. package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +23 -23
  79. package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +93 -93
  80. package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +15 -15
  81. package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +7 -7
  82. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  83. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  84. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  85. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  86. package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
  87. package/backend/src/infra/realtime/realtime.manager.ts +246 -0
  88. package/backend/src/infra/realtime/webhook-sender.ts +82 -0
  89. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  90. package/backend/src/infra/security/token.manager.ts +219 -0
  91. package/backend/src/infra/socket/socket.manager.ts +522 -0
  92. package/backend/src/providers/ai/openrouter.provider.ts +380 -0
  93. package/backend/src/providers/email/base.provider.ts +38 -0
  94. package/backend/src/providers/email/cloud.provider.ts +271 -0
  95. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  96. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  97. package/backend/src/providers/logs/local.provider.ts +185 -0
  98. package/backend/src/providers/oauth/apple.provider.ts +266 -0
  99. package/backend/src/providers/oauth/base.provider.ts +29 -0
  100. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  101. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  102. package/backend/src/providers/oauth/github.provider.ts +208 -0
  103. package/backend/src/providers/oauth/google.provider.ts +249 -0
  104. package/backend/src/providers/oauth/index.ts +8 -0
  105. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  106. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  107. package/backend/src/providers/oauth/x.provider.ts +202 -0
  108. package/backend/src/providers/storage/base.provider.ts +29 -0
  109. package/backend/src/providers/storage/local.provider.ts +103 -0
  110. package/backend/src/providers/storage/s3.provider.ts +313 -0
  111. package/backend/src/server.ts +317 -288
  112. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  113. package/backend/src/services/ai/ai-model.service.ts +60 -0
  114. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  115. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  116. package/backend/src/services/ai/helpers.ts +64 -0
  117. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  118. package/backend/src/services/ai/index.ts +13 -0
  119. package/backend/src/services/auth/auth-config.service.ts +250 -0
  120. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  121. package/backend/src/services/auth/auth.service.ts +1150 -0
  122. package/backend/src/services/auth/index.ts +4 -0
  123. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  124. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  125. package/backend/src/services/database/database-table.service.ts +802 -0
  126. package/backend/src/services/database/database.service.ts +127 -0
  127. package/backend/src/services/email/email.service.ts +73 -0
  128. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  129. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  130. package/backend/src/services/logs/log.service.ts +73 -0
  131. package/backend/src/services/realtime/index.ts +3 -0
  132. package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
  133. package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
  134. package/backend/src/services/realtime/realtime-message.service.ts +260 -0
  135. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  136. package/backend/src/services/storage/storage.service.ts +617 -0
  137. package/backend/src/services/usage/usage.service.ts +149 -0
  138. package/backend/src/types/auth.ts +77 -2
  139. package/backend/src/types/email.ts +8 -0
  140. package/backend/src/types/error-constants.ts +4 -0
  141. package/backend/src/types/logs.ts +0 -29
  142. package/backend/src/types/realtime.ts +18 -0
  143. package/backend/src/{core/socket/types.ts → types/socket.ts} +11 -36
  144. package/backend/src/utils/cookies.ts +35 -0
  145. package/backend/src/utils/environment.ts +9 -3
  146. package/backend/src/utils/logger.ts +20 -2
  147. package/backend/src/utils/s3-config-loader.ts +64 -0
  148. package/backend/src/utils/seed.ts +301 -205
  149. package/backend/src/utils/sql-parser.ts +91 -1
  150. package/backend/src/utils/utils.ts +114 -0
  151. package/backend/src/utils/validations.ts +40 -4
  152. package/backend/tests/README.md +133 -133
  153. package/backend/tests/cleanup-all-test-data.sh +230 -230
  154. package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
  155. package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
  156. package/backend/tests/local/test-ai-config.sh +129 -0
  157. package/backend/tests/local/test-ai-usage.sh +80 -0
  158. package/backend/tests/local/test-auth-router.sh +143 -143
  159. package/backend/tests/local/test-database-router.sh +222 -222
  160. package/backend/tests/local/test-e2e.sh +240 -240
  161. package/backend/tests/local/test-fk-errors.sh +96 -96
  162. package/backend/tests/local/test-functions.sh +123 -0
  163. package/backend/tests/local/test-id-field.sh +200 -200
  164. package/backend/tests/local/test-logs.sh +132 -0
  165. package/backend/tests/local/test-public-bucket.sh +264 -264
  166. package/backend/tests/local/test-secrets.sh +249 -247
  167. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
  168. package/backend/tests/local/test-traditional-rest.sh +208 -208
  169. package/backend/tests/manual/README.md +50 -50
  170. package/backend/tests/manual/create-large-table-simple.sql +10 -10
  171. package/backend/tests/manual/seed-large-table.sql +100 -100
  172. package/backend/tests/manual/setup-large-table-extras.sql +33 -33
  173. package/backend/tests/manual/test-bulk-upsert.sh +409 -409
  174. package/backend/tests/manual/test-database-advance.sh +296 -296
  175. package/backend/tests/manual/test-postgrest-stability.sh +191 -191
  176. package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
  177. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  178. package/backend/tests/manual/test-universal-storage.sh +263 -263
  179. package/backend/tests/manual/test-users.sql +17 -17
  180. package/backend/tests/run-all-tests.sh +139 -139
  181. package/backend/tests/setup.ts +0 -0
  182. package/backend/tests/test-config.sh +338 -302
  183. package/backend/tests/unit/analyze-query.test.ts +697 -0
  184. package/backend/tests/unit/cloud-token.test.ts +48 -0
  185. package/backend/tests/unit/constant.test.ts +8 -0
  186. package/backend/tests/unit/email.test.ts +372 -0
  187. package/backend/tests/unit/environment.test.ts +59 -0
  188. package/backend/tests/unit/helpers.test.ts +63 -0
  189. package/backend/tests/unit/logger.test.ts +22 -0
  190. package/backend/tests/unit/rate-limit.test.ts +154 -0
  191. package/backend/tests/unit/response.test.ts +58 -0
  192. package/backend/tests/unit/sql-parser.test.ts +74 -0
  193. package/backend/tests/unit/uuid.test.ts +21 -0
  194. package/backend/tests/unit/validations.test.ts +80 -0
  195. package/backend/tsconfig.json +22 -22
  196. package/backend/vitest.config.ts +11 -0
  197. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  198. package/claude-plugin/README.md +133 -0
  199. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  200. package/docker-compose.prod.yml +204 -144
  201. package/docker-compose.yml +232 -167
  202. package/docker-init/db/db-init.sql +97 -125
  203. package/docker-init/db/jwt.sql +5 -5
  204. package/docker-init/db/postgresql.conf +16 -16
  205. package/docker-init/logs/vector.yml +236 -0
  206. package/docs/README.md +44 -0
  207. package/docs/agent-docs/real-time.md +269 -0
  208. package/docs/changelog.mdx +119 -0
  209. package/docs/core-concepts/ai/architecture.mdx +373 -0
  210. package/docs/core-concepts/ai/sdk.mdx +213 -0
  211. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  212. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  213. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  214. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  215. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  216. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  217. package/docs/core-concepts/database/architecture.mdx +256 -0
  218. package/docs/core-concepts/database/sdk.mdx +382 -0
  219. package/docs/core-concepts/email/architecture.mdx +101 -0
  220. package/docs/core-concepts/email/sdk.mdx +53 -0
  221. package/docs/core-concepts/functions/architecture.mdx +105 -0
  222. package/docs/core-concepts/functions/sdk.mdx +184 -0
  223. package/docs/core-concepts/realtime/architecture.mdx +446 -0
  224. package/docs/core-concepts/realtime/sdk.mdx +409 -0
  225. package/docs/core-concepts/storage/architecture.mdx +243 -0
  226. package/docs/core-concepts/storage/sdk.mdx +253 -0
  227. package/docs/deployment/README.md +94 -0
  228. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  229. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  230. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  231. package/docs/deployment/deploy-to-render.md +441 -0
  232. package/docs/deprecated/insforge-auth-api.md +214 -214
  233. package/docs/deprecated/insforge-auth-sdk.md +99 -99
  234. package/docs/deprecated/insforge-db-api.md +358 -358
  235. package/docs/deprecated/insforge-db-sdk.md +139 -139
  236. package/docs/deprecated/insforge-debug-sdk.md +156 -156
  237. package/docs/deprecated/insforge-debug.md +64 -64
  238. package/docs/deprecated/insforge-instructions.md +123 -123
  239. package/docs/deprecated/insforge-project.md +117 -117
  240. package/docs/deprecated/insforge-storage-api.md +278 -278
  241. package/docs/deprecated/insforge-storage-sdk.md +158 -158
  242. package/docs/docs.json +232 -0
  243. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  244. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  245. package/docs/examples/framework-guides/react.mdx +165 -0
  246. package/docs/examples/framework-guides/svelte.mdx +153 -0
  247. package/docs/examples/framework-guides/vue.mdx +159 -0
  248. package/docs/examples/overview.mdx +67 -0
  249. package/docs/favicon.svg +19 -0
  250. package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
  251. package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
  252. package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
  253. package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
  254. package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
  255. package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
  256. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  257. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  258. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  259. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  260. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  261. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  262. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  263. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  264. package/docs/images/checks-passed.png +0 -0
  265. package/docs/images/dashboard-connect-expanded.png +0 -0
  266. package/docs/images/dashboard-connect.png +0 -0
  267. package/docs/images/hero-dark.png +0 -0
  268. package/docs/images/hero-light.png +0 -0
  269. package/docs/images/icons/ai.svg +4 -0
  270. package/docs/images/icons/auth.svg +1 -0
  271. package/docs/images/icons/database.svg +1 -0
  272. package/docs/images/icons/function.svg +1 -0
  273. package/docs/images/icons/storage.svg +1 -0
  274. package/docs/images/logos/nextjs.svg +4 -0
  275. package/docs/images/logos/nuxt.svg +4 -0
  276. package/docs/images/logos/react.svg +5 -0
  277. package/docs/images/logos/svelte.svg +4 -0
  278. package/docs/images/logos/vue.svg +5 -0
  279. package/docs/images/mcp-install.png +0 -0
  280. package/docs/images/onboarding-mcp.png +0 -0
  281. package/docs/insforge-instructions-sdk.md +89 -407
  282. package/docs/introduction.mdx +45 -0
  283. package/docs/logo/dark.svg +22 -0
  284. package/docs/logo/light.svg +20 -0
  285. package/docs/partnership.mdx +652 -0
  286. package/docs/quickstart.mdx +83 -0
  287. package/docs/showcase/2048-arena.png +0 -0
  288. package/docs/showcase/framegen-cloud.png +0 -0
  289. package/docs/showcase/line-connect-race.png +0 -0
  290. package/docs/showcase/moment-vibe.png +0 -0
  291. package/docs/showcase/national-flags.png +0 -0
  292. package/docs/showcase/pokemon-vibe.png +0 -0
  293. package/docs/showcase/pure-browse-buy.png +0 -0
  294. package/docs/showcase.mdx +52 -0
  295. package/docs/snippets/sdk-installation.mdx +22 -0
  296. package/docs/snippets/service-icons.mdx +27 -0
  297. package/eslint.config.js +10 -3
  298. package/examples/oauth/frontend-oauth-example.html +250 -250
  299. package/examples/response-examples.md +443 -443
  300. package/frontend/components.json +17 -17
  301. package/frontend/package.json +69 -63
  302. package/frontend/src/App.tsx +13 -82
  303. package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
  304. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
  305. package/frontend/src/assets/icons/checked.svg +3 -3
  306. package/frontend/src/assets/icons/connected.svg +3 -0
  307. package/frontend/src/assets/icons/error.svg +3 -3
  308. package/frontend/src/assets/icons/loader.svg +9 -0
  309. package/frontend/src/assets/icons/pencil.svg +4 -4
  310. package/frontend/src/assets/icons/refresh.svg +4 -4
  311. package/frontend/src/assets/icons/step_active.svg +3 -3
  312. package/frontend/src/assets/icons/step_inactive.svg +11 -11
  313. package/frontend/src/assets/icons/warning.svg +3 -3
  314. package/frontend/src/assets/logos/apple.svg +4 -0
  315. package/frontend/src/assets/logos/claude_code.svg +3 -3
  316. package/frontend/src/assets/logos/cline.svg +6 -6
  317. package/frontend/src/assets/logos/cursor.svg +20 -20
  318. package/frontend/src/assets/logos/discord.svg +8 -8
  319. package/frontend/src/assets/logos/facebook.svg +3 -0
  320. package/frontend/src/assets/logos/gemini.svg +19 -19
  321. package/frontend/src/assets/logos/github.svg +5 -5
  322. package/frontend/src/assets/logos/google.svg +13 -13
  323. package/frontend/src/assets/logos/grok.svg +10 -10
  324. package/frontend/src/assets/logos/insforge_dark.svg +15 -15
  325. package/frontend/src/assets/logos/insforge_light.svg +15 -15
  326. package/frontend/src/assets/logos/instagram.svg +2 -0
  327. package/frontend/src/assets/logos/linkedin.svg +3 -0
  328. package/frontend/src/assets/logos/microsoft.svg +1 -0
  329. package/frontend/src/assets/logos/openai.svg +10 -10
  330. package/frontend/src/assets/logos/roo_code.svg +9 -9
  331. package/frontend/src/assets/logos/spotify.svg +17 -0
  332. package/frontend/src/assets/logos/tiktok.svg +6 -0
  333. package/frontend/src/assets/logos/trae.svg +3 -3
  334. package/frontend/src/assets/logos/windsurf.svg +10 -10
  335. package/frontend/src/assets/logos/x.svg +3 -0
  336. package/frontend/src/components/Checkbox.tsx +27 -29
  337. package/frontend/src/components/CodeBlock.tsx +55 -2
  338. package/frontend/src/components/CodeEditor.tsx +92 -0
  339. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  340. package/frontend/src/components/ConnectCTA.tsx +38 -0
  341. package/frontend/src/components/CopyButton.tsx +52 -15
  342. package/frontend/src/components/ErrorState.tsx +1 -2
  343. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  344. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  345. package/frontend/src/components/JsonHighlight.tsx +21 -9
  346. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  347. package/frontend/src/components/PromptDialog.tsx +1 -4
  348. package/frontend/src/components/SearchInput.tsx +1 -2
  349. package/frontend/src/components/Stepper.tsx +53 -0
  350. package/frontend/src/components/ThemeToggle.tsx +3 -3
  351. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  352. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  353. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  354. package/frontend/src/components/datagrid/index.ts +23 -0
  355. package/frontend/src/components/index.ts +23 -30
  356. package/frontend/src/components/layout/AppHeader.tsx +131 -91
  357. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  358. package/frontend/src/components/layout/Layout.tsx +12 -23
  359. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  360. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  361. package/frontend/src/components/layout/index.ts +5 -0
  362. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  363. package/frontend/src/components/radix/index.ts +22 -0
  364. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  365. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  366. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  367. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  368. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  369. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  370. package/frontend/src/features/ai/components/index.ts +6 -0
  371. package/frontend/src/features/ai/helpers.ts +57 -71
  372. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  373. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  374. package/frontend/src/features/ai/pages/AIPage.tsx +166 -0
  375. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  376. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  377. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +54 -30
  378. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  379. package/frontend/src/features/auth/components/UsersDataGrid.tsx +50 -14
  380. package/frontend/src/features/auth/components/index.ts +5 -0
  381. package/frontend/src/features/auth/helpers.tsx +208 -0
  382. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  383. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  384. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  385. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  386. package/frontend/src/features/auth/index.ts +3 -2
  387. package/frontend/src/features/auth/pages/AuthMethodsPage.tsx +275 -0
  388. package/frontend/src/features/auth/pages/ConfigurationPage.tsx +395 -0
  389. package/frontend/src/features/auth/pages/UsersPage.tsx +257 -0
  390. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  391. package/frontend/src/features/auth/services/config.service.ts +19 -0
  392. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  393. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  394. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  395. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  396. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  397. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  398. package/frontend/src/features/dashboard/components/index.ts +4 -0
  399. package/frontend/src/features/dashboard/pages/DashboardPage.tsx +212 -0
  400. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  401. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  402. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  403. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  404. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  405. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  406. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  407. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  408. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  409. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  410. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  411. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  412. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  413. package/frontend/src/features/database/components/SQLModal.tsx +75 -0
  414. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  415. package/frontend/src/features/database/components/TableForm.tsx +28 -19
  416. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  417. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  418. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  419. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  420. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  421. package/frontend/src/features/database/components/index.ts +19 -0
  422. package/frontend/src/features/database/constants.ts +28 -2
  423. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  424. package/frontend/src/features/database/helpers.ts +2 -2
  425. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  426. package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
  427. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  428. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  429. package/frontend/src/features/database/hooks/useTables.ts +135 -0
  430. package/frontend/src/features/database/index.ts +7 -1
  431. package/frontend/src/features/database/pages/FunctionsPage.tsx +203 -0
  432. package/frontend/src/features/database/pages/IndexesPage.tsx +228 -0
  433. package/frontend/src/features/database/pages/PoliciesPage.tsx +237 -0
  434. package/frontend/src/features/database/pages/SQLEditorPage.tsx +382 -0
  435. package/frontend/src/features/database/{page/DatabasePage.tsx → pages/TablesPage.tsx} +168 -209
  436. package/frontend/src/features/database/pages/TemplatesPage.tsx +39 -0
  437. package/frontend/src/features/database/pages/TriggersPage.tsx +230 -0
  438. package/frontend/src/features/database/services/advance.service.ts +40 -0
  439. package/frontend/src/features/database/services/database.service.ts +33 -194
  440. package/frontend/src/features/database/services/record.service.ts +219 -0
  441. package/frontend/src/features/database/services/table.service.ts +58 -0
  442. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  443. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  444. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  445. package/frontend/src/features/database/templates/index.ts +34 -0
  446. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  447. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  448. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  449. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  450. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  451. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  452. package/frontend/src/features/functions/components/index.ts +5 -0
  453. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  454. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  455. package/frontend/src/features/functions/pages/FunctionsPage.tsx +148 -0
  456. package/frontend/src/features/functions/{components/SecretsContent.tsx → pages/SecretsPage.tsx} +19 -21
  457. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  458. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  459. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  460. package/frontend/src/features/login/pages/CloudLoginPage.tsx +118 -0
  461. package/frontend/src/features/login/{page → pages}/LoginPage.tsx +16 -23
  462. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  463. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  464. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  465. package/frontend/src/features/logs/components/index.ts +2 -0
  466. package/frontend/src/features/logs/helpers.ts +24 -0
  467. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  468. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  469. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  470. package/frontend/src/features/logs/hooks/useMcpUsage.ts +128 -0
  471. package/frontend/src/features/logs/index.ts +8 -2
  472. package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +91 -38
  473. package/frontend/src/features/logs/pages/LogsPage.tsx +152 -0
  474. package/frontend/src/features/logs/pages/MCPLogsPage.tsx +84 -0
  475. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  476. package/frontend/src/features/logs/services/log.service.ts +15 -110
  477. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  478. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  479. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  480. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  481. package/frontend/src/features/onboard/components/index.ts +4 -0
  482. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  483. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  484. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  485. package/frontend/src/features/onboard/index.ts +13 -3
  486. package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
  487. package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
  488. package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
  489. package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
  490. package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
  491. package/frontend/src/features/realtime/index.ts +11 -0
  492. package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
  493. package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
  494. package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
  495. package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
  496. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  497. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  498. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  499. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  500. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  501. package/frontend/src/features/storage/components/index.ts +12 -0
  502. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  503. package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +41 -143
  504. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  505. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  506. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  507. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  508. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  509. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  510. package/frontend/src/features/visualizer/pages/VisualizerPage.tsx +97 -0
  511. package/frontend/src/index.css +1 -0
  512. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  513. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  514. package/frontend/src/lib/contexts/SocketContext.tsx +123 -80
  515. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  516. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  517. package/frontend/src/lib/routing/AppRoutes.tsx +99 -0
  518. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  519. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  520. package/frontend/src/lib/utils/menuItems.ts +207 -0
  521. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  522. package/frontend/src/lib/utils/utils.ts +32 -1
  523. package/frontend/src/vite-env.d.ts +1 -0
  524. package/frontend/tsconfig.json +25 -25
  525. package/frontend/tsconfig.node.json +9 -9
  526. package/frontend/vite.config.ts +5 -3
  527. package/functions/deno.json +24 -24
  528. package/functions/server.ts +315 -290
  529. package/functions/worker-template.js +15 -4
  530. package/i18n/README.ar.md +130 -0
  531. package/i18n/README.de.md +130 -0
  532. package/i18n/README.es.md +154 -0
  533. package/i18n/README.fr.md +134 -0
  534. package/i18n/README.hi.md +129 -0
  535. package/i18n/README.ja.md +174 -0
  536. package/i18n/README.ko.md +137 -0
  537. package/i18n/README.pt-BR.md +131 -0
  538. package/i18n/README.ru.md +129 -0
  539. package/i18n/README.zh-CN.md +133 -0
  540. package/openapi/ai.yaml +715 -688
  541. package/openapi/auth.yaml +1244 -563
  542. package/openapi/email.yaml +158 -0
  543. package/openapi/functions.yaml +475 -475
  544. package/openapi/health.yaml +29 -29
  545. package/openapi/logs.yaml +223 -223
  546. package/openapi/metadata.yaml +177 -177
  547. package/openapi/realtime.yaml +699 -0
  548. package/openapi/records.yaml +381 -381
  549. package/openapi/secrets.yaml +370 -370
  550. package/openapi/storage.yaml +875 -875
  551. package/openapi/tables.yaml +463 -463
  552. package/package.json +97 -88
  553. package/shared-schemas/package.json +31 -31
  554. package/shared-schemas/src/ai-api.schema.ts +34 -58
  555. package/shared-schemas/src/ai.schema.ts +63 -54
  556. package/shared-schemas/src/auth-api.schema.ts +352 -193
  557. package/shared-schemas/src/auth.schema.ts +43 -7
  558. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  559. package/shared-schemas/src/database-api.schema.ts +35 -4
  560. package/shared-schemas/src/database.schema.ts +40 -1
  561. package/shared-schemas/src/docs.schema.ts +26 -0
  562. package/shared-schemas/src/email-api.schema.ts +30 -0
  563. package/shared-schemas/src/index.ts +5 -0
  564. package/shared-schemas/src/logs-api.schema.ts +7 -1
  565. package/shared-schemas/src/logs.schema.ts +26 -0
  566. package/shared-schemas/src/metadata.schema.ts +18 -4
  567. package/shared-schemas/src/realtime-api.schema.ts +111 -0
  568. package/shared-schemas/src/realtime.schema.ts +143 -0
  569. package/shared-schemas/tsconfig.json +21 -21
  570. package/tsconfig.json +7 -7
  571. package/zeabur/README.md +13 -0
  572. package/zeabur/template.yml +1032 -0
  573. package/.github/workflows/deploy-aws.yml +0 -130
  574. package/backend/src/api/routes/agent.ts +0 -29
  575. package/backend/src/api/routes/auth.oauth.ts +0 -482
  576. package/backend/src/api/routes/auth.ts +0 -386
  577. package/backend/src/api/routes/docs.ts +0 -66
  578. package/backend/src/api/routes/functions.ts +0 -183
  579. package/backend/src/api/routes/openapi.ts +0 -82
  580. package/backend/src/api/routes/usage.ts +0 -96
  581. package/backend/src/core/ai/client.ts +0 -242
  582. package/backend/src/core/ai/model.ts +0 -117
  583. package/backend/src/core/auth/auth.ts +0 -780
  584. package/backend/src/core/database/manager.ts +0 -178
  585. package/backend/src/core/database/table.ts +0 -772
  586. package/backend/src/core/documentation/agent.ts +0 -689
  587. package/backend/src/core/documentation/openapi.ts +0 -856
  588. package/backend/src/core/logs/analytics.ts +0 -76
  589. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  590. package/backend/src/core/socket/socket.ts +0 -388
  591. package/backend/src/core/storage/storage.ts +0 -923
  592. package/backend/src/utils/cloud-token.ts +0 -39
  593. package/backend/src/utils/helpers.ts +0 -49
  594. package/backend/src/utils/uuid.ts +0 -9
  595. package/backend/tests/manual/test-better-auth.sh +0 -303
  596. package/docker-init/db/logs.sql +0 -9
  597. package/frontend/README.md +0 -112
  598. package/frontend/src/components/datagrid/index.tsx +0 -20
  599. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  600. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  601. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  602. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  603. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  604. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  605. package/frontend/src/features/ai/page/AIPage.tsx +0 -178
  606. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  607. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  608. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  609. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  610. package/frontend/src/features/dashboard/page/DashboardPage.tsx +0 -194
  611. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  612. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  613. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  614. package/frontend/src/features/functions/page/FunctionsPage.tsx +0 -28
  615. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  616. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  617. package/frontend/src/features/login/page/CloudLoginPage.tsx +0 -93
  618. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  619. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  620. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  621. package/frontend/src/features/metadata/index.ts +0 -0
  622. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  623. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  624. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  625. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  626. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  627. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  628. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  629. package/frontend/src/features/onboard/types.ts +0 -8
  630. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +0 -127
  631. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  632. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  633. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  634. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  635. /package/frontend/src/{features/metadata → lib}/services/metadata.service.ts +0 -0
@@ -0,0 +1,617 @@
1
+ import path from 'path';
2
+ import { Pool } from 'pg';
3
+ import { DatabaseManager } from '@/infra/database/database.manager.js';
4
+ import { StorageRecord } from '@/types/storage.js';
5
+ import {
6
+ StorageBucketSchema,
7
+ StorageFileSchema,
8
+ StorageMetadataSchema,
9
+ } from '@insforge/shared-schemas';
10
+ import { StorageProvider } from '@/providers/storage/base.provider.js';
11
+ import { LocalStorageProvider } from '@/providers/storage/local.provider.js';
12
+ import { S3StorageProvider } from '@/providers/storage/s3.provider.js';
13
+ import logger from '@/utils/logger.js';
14
+ import { ADMIN_ID } from '@/utils/constants.js';
15
+ import { AppError } from '@/api/middlewares/error.js';
16
+ import { ERROR_CODES } from '@/types/error-constants.js';
17
+ import { escapeSqlLikePattern, escapeRegexPattern } from '@/utils/validations.js';
18
+ import { getApiBaseUrl } from '@/utils/environment.js';
19
+
20
+ const DEFAULT_LIST_LIMIT = 100;
21
+ const GIGABYTE_IN_BYTES = 1024 * 1024 * 1024;
22
+ const PUBLIC_BUCKET_EXPIRY = 0; // Public buckets don't expire
23
+ const PRIVATE_BUCKET_EXPIRY = 3600; // Private buckets expire in 1 hour
24
+
25
+ export class StorageService {
26
+ private static instance: StorageService;
27
+ private provider: StorageProvider;
28
+ private pool: Pool | null = null;
29
+
30
+ private constructor() {
31
+ const s3Bucket = process.env.AWS_S3_BUCKET;
32
+ const appKey = process.env.APP_KEY || 'local';
33
+
34
+ if (s3Bucket) {
35
+ // Use S3 backend
36
+ this.provider = new S3StorageProvider(
37
+ s3Bucket,
38
+ appKey,
39
+ process.env.AWS_REGION || 'us-east-2'
40
+ );
41
+ } else {
42
+ // Use local filesystem backend
43
+ const baseDir = process.env.STORAGE_DIR || path.resolve(process.cwd(), 'insforge-storage');
44
+ this.provider = new LocalStorageProvider(baseDir);
45
+ }
46
+ }
47
+
48
+ private getPool(): Pool {
49
+ if (!this.pool) {
50
+ this.pool = DatabaseManager.getInstance().getPool();
51
+ }
52
+ return this.pool;
53
+ }
54
+
55
+ static getInstance(): StorageService {
56
+ if (!StorageService.instance) {
57
+ StorageService.instance = new StorageService();
58
+ }
59
+ return StorageService.instance;
60
+ }
61
+
62
+ async initialize(): Promise<void> {
63
+ await this.provider.initialize();
64
+ }
65
+
66
+ private validateBucketName(bucket: string): void {
67
+ // Simple validation: alphanumeric, hyphens, underscores
68
+ if (!/^[a-zA-Z0-9_-]+$/.test(bucket)) {
69
+ throw new Error('Invalid bucket name. Use only letters, numbers, hyphens, and underscores.');
70
+ }
71
+ }
72
+
73
+ private validateKey(key: string): void {
74
+ // Prevent directory traversal
75
+ if (key.includes('..') || key.startsWith('/')) {
76
+ throw new Error('Invalid key. Cannot use ".." or start with "/"');
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Generate a unique object key with timestamp and random string
82
+ * @param originalFilename - The original filename from the upload
83
+ * @returns Generated unique key
84
+ */
85
+ generateObjectKey(originalFilename: string): string {
86
+ const timestamp = Date.now();
87
+ const randomStr = Math.random().toString(36).substring(2, 8);
88
+ const fileExt = originalFilename ? path.extname(originalFilename) : '';
89
+ const baseName = originalFilename ? path.basename(originalFilename, fileExt) : 'file';
90
+ const sanitizedBaseName = baseName.replace(/[^a-zA-Z0-9-_]/g, '-').substring(0, 32);
91
+ const objectKey = `${sanitizedBaseName}-${timestamp}-${randomStr}${fileExt}`;
92
+
93
+ return objectKey;
94
+ }
95
+
96
+ /**
97
+ * Generate the next available key for a file, using (1), (2), (3) pattern if duplicates exist
98
+ * @param bucket - The bucket name
99
+ * @param originalKey - The original filename
100
+ * @returns The next available key
101
+ */
102
+ private async generateNextAvailableKey(bucket: string, originalKey: string): Promise<string> {
103
+ // Parse filename and extension for potential auto-renaming
104
+ const lastDotIndex = originalKey.lastIndexOf('.');
105
+ const baseName = lastDotIndex > 0 ? originalKey.substring(0, lastDotIndex) : originalKey;
106
+ const extension = lastDotIndex > 0 ? originalKey.substring(lastDotIndex) : '';
107
+
108
+ // Use efficient SQL query to find the highest existing counter
109
+ // This query finds all files matching the pattern and extracts the counter number
110
+ const result = await this.getPool().query(
111
+ `
112
+ SELECT key FROM _storage
113
+ WHERE bucket = $1
114
+ AND (key = $2 OR key LIKE $3)
115
+ `,
116
+ [
117
+ bucket,
118
+ originalKey,
119
+ `${escapeSqlLikePattern(baseName)} (%)${escapeSqlLikePattern(extension)}`,
120
+ ]
121
+ );
122
+
123
+ const existingFiles = result.rows;
124
+ let finalKey = originalKey;
125
+
126
+ if (existingFiles.length) {
127
+ // Extract counter numbers from existing files
128
+ let incrementNumber = 0;
129
+ // This regex is used to match the counter number in the filename, extract the increment number
130
+ const counterRegex = new RegExp(
131
+ `^${escapeRegexPattern(baseName)} \\((\\d+)\\)${escapeRegexPattern(extension)}$`
132
+ );
133
+
134
+ for (const file of existingFiles as { key: string }[]) {
135
+ if (file.key === originalKey) {
136
+ incrementNumber = Math.max(incrementNumber, 0); // Original file exists, so we need at least (1)
137
+ } else {
138
+ const match = file.key.match(counterRegex);
139
+ if (match) {
140
+ incrementNumber = Math.max(incrementNumber, parseInt(match[1], 10));
141
+ }
142
+ }
143
+ }
144
+
145
+ // Generate the next available filename
146
+ finalKey = `${baseName} (${incrementNumber + 1})${extension}`;
147
+ }
148
+
149
+ return finalKey;
150
+ }
151
+
152
+ async putObject(
153
+ bucket: string,
154
+ originalKey: string,
155
+ file: Express.Multer.File,
156
+ userId?: string
157
+ ): Promise<StorageFileSchema> {
158
+ this.validateBucketName(bucket);
159
+ this.validateKey(originalKey);
160
+
161
+ // Generate next available key using (1), (2), (3) pattern if duplicates exist
162
+ const finalKey = await this.generateNextAvailableKey(bucket, originalKey);
163
+
164
+ // Save file using backend
165
+ await this.provider.putObject(bucket, finalKey, file);
166
+
167
+ const client = await this.getPool().connect();
168
+ try {
169
+ // Save metadata to database and return the timestamp in one operation
170
+ const result = await client.query(
171
+ `
172
+ INSERT INTO _storage (bucket, key, size, mime_type, uploaded_by)
173
+ VALUES ($1, $2, $3, $4, $5)
174
+ RETURNING uploaded_at as "uploadedAt"
175
+ `,
176
+ [
177
+ bucket,
178
+ finalKey,
179
+ file.size,
180
+ file.mimetype || null,
181
+ userId && userId !== ADMIN_ID ? userId : null,
182
+ ]
183
+ );
184
+
185
+ if (!result.rows[0]) {
186
+ throw new Error(`Failed to retrieve upload timestamp for ${bucket}/${finalKey}`);
187
+ }
188
+
189
+ return {
190
+ bucket,
191
+ key: finalKey,
192
+ size: file.size,
193
+ mimeType: file.mimetype,
194
+ uploadedAt: result.rows[0].uploadedAt,
195
+ url: `${getApiBaseUrl()}/api/storage/buckets/${bucket}/objects/${encodeURIComponent(finalKey)}`,
196
+ };
197
+ } finally {
198
+ client.release();
199
+ }
200
+ }
201
+
202
+ async getObject(
203
+ bucket: string,
204
+ key: string
205
+ ): Promise<{ file: Buffer; metadata: StorageFileSchema } | null> {
206
+ this.validateBucketName(bucket);
207
+ this.validateKey(key);
208
+
209
+ const result = await this.getPool().query(
210
+ 'SELECT * FROM _storage WHERE bucket = $1 AND key = $2',
211
+ [bucket, key]
212
+ );
213
+
214
+ const metadata = result.rows[0] as StorageRecord | undefined;
215
+
216
+ if (!metadata) {
217
+ return null;
218
+ }
219
+
220
+ const file = await this.provider.getObject(bucket, key);
221
+ if (!file) {
222
+ return null;
223
+ }
224
+
225
+ return {
226
+ file,
227
+ metadata: {
228
+ key: metadata.key,
229
+ bucket: metadata.bucket,
230
+ size: metadata.size,
231
+ mimeType: metadata.mime_type,
232
+ uploadedAt: metadata.uploaded_at,
233
+ url: `${getApiBaseUrl()}/api/storage/buckets/${bucket}/objects/${encodeURIComponent(key)}`,
234
+ },
235
+ };
236
+ }
237
+
238
+ async deleteObject(
239
+ bucket: string,
240
+ key: string,
241
+ userId?: string,
242
+ isAdmin?: boolean
243
+ ): Promise<boolean> {
244
+ this.validateBucketName(bucket);
245
+ this.validateKey(key);
246
+
247
+ const client = await this.getPool().connect();
248
+ try {
249
+ // Check permissions
250
+ if (!isAdmin) {
251
+ const fileResult = await client.query(
252
+ 'SELECT uploaded_by FROM _storage WHERE bucket = $1 AND key = $2',
253
+ [bucket, key]
254
+ );
255
+
256
+ const file = fileResult.rows[0] as { uploaded_by: string | null } | undefined;
257
+
258
+ if (!file) {
259
+ return false; // File doesn't exist
260
+ }
261
+
262
+ // Check if user owns the file
263
+ if (userId && file.uploaded_by !== userId) {
264
+ throw new AppError(
265
+ 'Permission denied: You can only delete files you uploaded',
266
+ 403,
267
+ ERROR_CODES.FORBIDDEN
268
+ );
269
+ }
270
+ }
271
+
272
+ // Delete file using backend
273
+ await this.provider.deleteObject(bucket, key);
274
+
275
+ // Delete from database
276
+ const result = await client.query('DELETE FROM _storage WHERE bucket = $1 AND key = $2', [
277
+ bucket,
278
+ key,
279
+ ]);
280
+
281
+ return result.rowCount !== null && result.rowCount > 0;
282
+ } finally {
283
+ client.release();
284
+ }
285
+ }
286
+
287
+ async listObjects(
288
+ bucket: string,
289
+ prefix?: string,
290
+ limit: number = DEFAULT_LIST_LIMIT,
291
+ offset: number = 0,
292
+ searchQuery?: string
293
+ ): Promise<{ objects: StorageFileSchema[]; total: number }> {
294
+ this.validateBucketName(bucket);
295
+
296
+ const client = await this.getPool().connect();
297
+ try {
298
+ let query = 'SELECT * FROM _storage WHERE bucket = $1';
299
+ let countQuery = 'SELECT COUNT(*) as count FROM _storage WHERE bucket = $1';
300
+ const params: (string | number)[] = [bucket];
301
+ let paramIndex = 2;
302
+
303
+ if (prefix) {
304
+ query += ` AND key LIKE $${paramIndex}`;
305
+ countQuery += ` AND key LIKE $${paramIndex}`;
306
+ params.push(`${prefix}%`);
307
+ paramIndex++;
308
+ }
309
+
310
+ // Add search functionality for file names (key field)
311
+ if (searchQuery && searchQuery.trim()) {
312
+ query += ` AND key LIKE $${paramIndex}`;
313
+ countQuery += ` AND key LIKE $${paramIndex}`;
314
+ const searchPattern = `%${searchQuery.trim()}%`;
315
+ params.push(searchPattern);
316
+ paramIndex++;
317
+ }
318
+
319
+ query += ` ORDER BY key LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`;
320
+ const queryParams = [...params, limit, offset];
321
+
322
+ const objectsResult = await client.query(query, queryParams);
323
+ const totalResult = await client.query(countQuery, params);
324
+
325
+ return {
326
+ objects: objectsResult.rows.map((obj) => ({
327
+ ...obj,
328
+ mimeType: obj.mime_type,
329
+ uploadedAt: obj.uploaded_at,
330
+ url: `${getApiBaseUrl()}/api/storage/buckets/${bucket}/objects/${encodeURIComponent(obj.key)}`,
331
+ })),
332
+ total: parseInt(totalResult.rows[0].count, 10),
333
+ };
334
+ } finally {
335
+ client.release();
336
+ }
337
+ }
338
+
339
+ async isBucketPublic(bucket: string): Promise<boolean> {
340
+ const result = await this.getPool().query(
341
+ 'SELECT public FROM _storage_buckets WHERE name = $1',
342
+ [bucket]
343
+ );
344
+ return result.rows[0]?.public || false;
345
+ }
346
+
347
+ async updateBucketVisibility(bucket: string, isPublic: boolean): Promise<void> {
348
+ const client = await this.getPool().connect();
349
+ try {
350
+ // Check if bucket exists
351
+ const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
352
+ bucket,
353
+ ]);
354
+
355
+ if (!bucketResult.rows[0]) {
356
+ throw new Error(`Bucket "${bucket}" does not exist`);
357
+ }
358
+
359
+ // Update bucket visibility in _storage_buckets table
360
+ await client.query(
361
+ 'UPDATE _storage_buckets SET public = $1, updated_at = CURRENT_TIMESTAMP WHERE name = $2',
362
+ [isPublic, bucket]
363
+ );
364
+
365
+ // Update storage metadata
366
+ // Metadata is now updated on-demand
367
+ } finally {
368
+ client.release();
369
+ }
370
+ }
371
+
372
+ async listBuckets(): Promise<StorageBucketSchema[]> {
373
+ // Get all buckets with their metadata from _storage_buckets table
374
+ const result = await this.getPool().query(
375
+ 'SELECT name, public, created_at as "createdAt" FROM _storage_buckets ORDER BY name'
376
+ );
377
+
378
+ return result.rows as StorageBucketSchema[];
379
+ }
380
+
381
+ async createBucket(bucket: string, isPublic: boolean = true): Promise<void> {
382
+ this.validateBucketName(bucket);
383
+
384
+ const client = await this.getPool().connect();
385
+ try {
386
+ // Check if bucket already exists
387
+ const existing = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
388
+ bucket,
389
+ ]);
390
+
391
+ if (existing.rows[0]) {
392
+ throw new Error(`Bucket "${bucket}" already exists`);
393
+ }
394
+
395
+ // Insert bucket into _storage_buckets table
396
+ await client.query('INSERT INTO _storage_buckets (name, public) VALUES ($1, $2)', [
397
+ bucket,
398
+ isPublic,
399
+ ]);
400
+
401
+ // Create bucket using backend
402
+ await this.provider.createBucket(bucket);
403
+
404
+ // Update storage metadata
405
+ // Metadata is now updated on-demand
406
+ } finally {
407
+ client.release();
408
+ }
409
+ }
410
+
411
+ async deleteBucket(bucket: string): Promise<boolean> {
412
+ this.validateBucketName(bucket);
413
+
414
+ const client = await this.getPool().connect();
415
+ try {
416
+ // Check if bucket exists
417
+ const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
418
+ bucket,
419
+ ]);
420
+
421
+ if (!bucketResult.rows[0]) {
422
+ return false;
423
+ }
424
+
425
+ // Delete bucket using backend (handles all files)
426
+ await this.provider.deleteBucket(bucket);
427
+
428
+ // Delete from storage table (cascade will handle _storage entries)
429
+ await client.query('DELETE FROM _storage_buckets WHERE name = $1', [bucket]);
430
+
431
+ // Update storage metadata
432
+ // Metadata is now updated on-demand
433
+
434
+ return true;
435
+ } finally {
436
+ client.release();
437
+ }
438
+ }
439
+
440
+ // New methods for universal upload/download strategies
441
+ async getUploadStrategy(
442
+ bucket: string,
443
+ metadata: {
444
+ filename: string;
445
+ contentType?: string;
446
+ size?: number;
447
+ }
448
+ ) {
449
+ this.validateBucketName(bucket);
450
+
451
+ const client = await this.getPool().connect();
452
+ try {
453
+ // Check if bucket exists
454
+ const bucketResult = await client.query('SELECT name FROM _storage_buckets WHERE name = $1', [
455
+ bucket,
456
+ ]);
457
+
458
+ if (!bucketResult.rows[0]) {
459
+ throw new Error(`Bucket "${bucket}" does not exist`);
460
+ }
461
+
462
+ // Generate next available key using (1), (2), (3) pattern if duplicates exist
463
+ const key = await this.generateNextAvailableKey(bucket, metadata.filename);
464
+ return this.provider.getUploadStrategy(bucket, key, metadata);
465
+ } finally {
466
+ client.release();
467
+ }
468
+ }
469
+
470
+ async getDownloadStrategy(bucket: string, key: string) {
471
+ this.validateBucketName(bucket);
472
+ this.validateKey(key);
473
+
474
+ // Check if bucket is public
475
+ const isPublic = await this.isBucketPublic(bucket);
476
+
477
+ // Auto-calculate expiry based on bucket visibility if not provided
478
+ const expiresIn = isPublic ? PUBLIC_BUCKET_EXPIRY : PRIVATE_BUCKET_EXPIRY;
479
+
480
+ return this.provider.getDownloadStrategy(bucket, key, expiresIn, isPublic);
481
+ }
482
+
483
+ async confirmUpload(
484
+ bucket: string,
485
+ key: string,
486
+ metadata: {
487
+ size: number;
488
+ contentType?: string;
489
+ etag?: string;
490
+ },
491
+ userId?: string
492
+ ): Promise<StorageFileSchema> {
493
+ this.validateBucketName(bucket);
494
+ this.validateKey(key);
495
+
496
+ // Verify the file exists in storage
497
+ const exists = await this.provider.verifyObjectExists(bucket, key);
498
+ if (!exists) {
499
+ throw new Error(`Upload not found for key "${key}" in bucket "${bucket}"`);
500
+ }
501
+
502
+ const client = await this.getPool().connect();
503
+ try {
504
+ // Check if already confirmed
505
+ const existingResult = await client.query(
506
+ 'SELECT key FROM _storage WHERE bucket = $1 AND key = $2',
507
+ [bucket, key]
508
+ );
509
+
510
+ if (existingResult.rows[0]) {
511
+ throw new Error(`File "${key}" already confirmed in bucket "${bucket}"`);
512
+ }
513
+
514
+ // Save metadata to database and return the timestamp in one operation
515
+ const result = await client.query(
516
+ `
517
+ INSERT INTO _storage (bucket, key, size, mime_type, uploaded_by)
518
+ VALUES ($1, $2, $3, $4, $5)
519
+ RETURNING uploaded_at as "uploadedAt"
520
+ `,
521
+ [
522
+ bucket,
523
+ key,
524
+ metadata.size,
525
+ metadata.contentType || null,
526
+ userId && userId !== ADMIN_ID ? userId : null,
527
+ ]
528
+ );
529
+
530
+ if (!result.rows[0]) {
531
+ throw new Error(`Failed to retrieve upload timestamp for ${bucket}/${key}`);
532
+ }
533
+
534
+ return {
535
+ bucket,
536
+ key,
537
+ size: metadata.size,
538
+ mimeType: metadata.contentType,
539
+ uploadedAt: result.rows[0].uploadedAt,
540
+ url: `${getApiBaseUrl()}/api/storage/buckets/${bucket}/objects/${encodeURIComponent(key)}`,
541
+ };
542
+ } finally {
543
+ client.release();
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Get storage metadata
549
+ */
550
+ async getMetadata(): Promise<StorageMetadataSchema> {
551
+ // Get storage buckets from _storage_buckets table
552
+ const result = await this.getPool().query(
553
+ 'SELECT name, public, created_at as "createdAt" FROM _storage_buckets ORDER BY name'
554
+ );
555
+
556
+ const storageBuckets = result.rows as StorageBucketSchema[];
557
+
558
+ // Get object counts for each bucket
559
+ const bucketsObjectCountMap = await this.getBucketsObjectCount();
560
+ const storageSize = await this.getStorageSizeInGB();
561
+
562
+ return {
563
+ buckets: storageBuckets.map((bucket) => ({
564
+ ...bucket,
565
+ objectCount: bucketsObjectCountMap.get(bucket.name) ?? 0,
566
+ })),
567
+ totalSizeInGB: storageSize,
568
+ };
569
+ }
570
+
571
+ private async getBucketsObjectCount(): Promise<Map<string, number>> {
572
+ try {
573
+ // Query to get object count for each bucket
574
+ const result = await this.getPool().query(
575
+ 'SELECT bucket, COUNT(*) as count FROM _storage GROUP BY bucket'
576
+ );
577
+
578
+ const bucketCounts = result.rows as { bucket: string; count: string }[];
579
+
580
+ // Convert to Map for easy lookup
581
+ const countMap = new Map<string, number>();
582
+ bucketCounts.forEach((row) => {
583
+ countMap.set(row.bucket, parseInt(row.count, 10));
584
+ });
585
+
586
+ return countMap;
587
+ } catch (error) {
588
+ logger.error('Error getting bucket object counts', {
589
+ error: error instanceof Error ? error.message : String(error),
590
+ });
591
+ // Return empty map on error
592
+ return new Map<string, number>();
593
+ }
594
+ }
595
+
596
+ private async getStorageSizeInGB(): Promise<number> {
597
+ try {
598
+ // Query the _storage table to sum all file sizes
599
+ const result = await this.getPool().query(
600
+ `
601
+ SELECT COALESCE(SUM(size), 0) as total_size
602
+ FROM _storage
603
+ `
604
+ );
605
+
606
+ const totalSize = result.rows[0]?.total_size || 0;
607
+
608
+ // Convert bytes to GB
609
+ return Number(totalSize) / GIGABYTE_IN_BYTES;
610
+ } catch (error) {
611
+ logger.error('Error getting storage size', {
612
+ error: error instanceof Error ? error.message : String(error),
613
+ });
614
+ return 0;
615
+ }
616
+ }
617
+ }