insforge 0.3.2 → 1.2.10

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 (507) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.cursor/rules/cursor-rules.mdc +94 -0
  3. package/.dockerignore +3 -0
  4. package/.env.example +33 -4
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +13 -60
  6. package/.github/ISSUE_TEMPLATE/config.yml +2 -2
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +10 -63
  8. package/.github/PULL_REQUEST_TEMPLATE.md +7 -0
  9. package/.github/workflows/build-image.yml +2 -1
  10. package/.github/workflows/e2e.yml +63 -0
  11. package/CHANGELOG.md +41 -0
  12. package/CLAUDE_PLUGIN.md +104 -0
  13. package/CODE_OF_CONDUCT.md +128 -0
  14. package/CONTRIBUTING.md +1 -1
  15. package/Dockerfile +4 -1
  16. package/README.md +66 -18
  17. package/assets/mcpInstallv2.png +0 -0
  18. package/assets/sampleResponse.png +0 -0
  19. package/auth/index.html +13 -0
  20. package/auth/package.json +28 -0
  21. package/auth/public/favicon.ico +0 -0
  22. package/auth/src/App.tsx +33 -0
  23. package/auth/src/components/ErrorCard.tsx +37 -0
  24. package/auth/src/components/Layout.tsx +13 -0
  25. package/auth/src/index.css +19 -0
  26. package/auth/src/lib/broadcastService.ts +115 -0
  27. package/auth/src/lib/utils.ts +11 -0
  28. package/auth/src/main.tsx +22 -0
  29. package/auth/src/pages/ForgotPasswordPage.tsx +11 -0
  30. package/auth/src/pages/ResetPasswordPage.tsx +11 -0
  31. package/auth/src/pages/SignInPage.tsx +57 -0
  32. package/auth/src/pages/SignUpPage.tsx +57 -0
  33. package/auth/src/pages/VerifyEmailPage.tsx +20 -0
  34. package/auth/src/vite-env.d.ts +10 -0
  35. package/auth/tsconfig.json +32 -0
  36. package/auth/tsconfig.node.json +11 -0
  37. package/auth/vite.config.ts +25 -0
  38. package/backend/package.json +9 -9
  39. package/backend/src/api/{middleware → middlewares}/auth.ts +8 -9
  40. package/backend/src/api/middlewares/rate-limiters.ts +127 -0
  41. package/backend/src/api/routes/{ai.ts → ai/index.routes.ts} +20 -24
  42. package/backend/src/api/routes/auth/index.routes.ts +570 -0
  43. package/backend/src/api/routes/auth/oauth.routes.ts +448 -0
  44. package/backend/src/api/routes/{database.advance.ts → database/advance.routes.ts} +107 -65
  45. package/backend/src/api/routes/database/index.routes.ts +13 -0
  46. package/backend/src/api/routes/{database.records.ts → database/records.routes.ts} +22 -8
  47. package/backend/src/api/routes/{database.tables.ts → database/tables.routes.ts} +20 -23
  48. package/backend/src/api/routes/docs/index.routes.ts +76 -0
  49. package/backend/src/api/routes/functions/index.routes.ts +188 -0
  50. package/backend/src/api/routes/{logs.ts → logs/index.routes.ts} +25 -30
  51. package/backend/src/api/routes/{metadata.ts → metadata/index.routes.ts} +21 -31
  52. package/backend/src/api/routes/{secrets.ts → secrets/index.routes.ts} +27 -22
  53. package/backend/src/api/routes/{storage.ts → storage/index.routes.ts} +34 -53
  54. package/backend/src/api/routes/usage/index.routes.ts +89 -0
  55. package/backend/src/infra/config/app.config.ts +51 -0
  56. package/backend/src/{core/database/manager.ts → infra/database/database.manager.ts} +76 -85
  57. package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -0
  58. package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +8 -0
  59. package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +60 -0
  60. package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -0
  61. package/backend/src/{core/secrets/encryption.ts → infra/security/encryption.manager.ts} +3 -2
  62. package/backend/src/infra/security/token.manager.ts +125 -0
  63. package/backend/src/{core/socket/socket.ts → infra/socket/socket.manager.ts} +15 -15
  64. package/backend/src/providers/ai/openrouter.provider.ts +377 -0
  65. package/backend/src/providers/email/base.provider.ts +41 -0
  66. package/backend/src/providers/email/cloud.provider.ts +187 -0
  67. package/backend/src/{core/logs/providers → providers/logs}/base.provider.ts +11 -11
  68. package/backend/src/{core/logs/providers → providers/logs}/cloudwatch.provider.ts +61 -38
  69. package/backend/src/providers/logs/local.provider.ts +185 -0
  70. package/backend/src/providers/oauth/base.provider.ts +29 -0
  71. package/backend/src/providers/oauth/discord.provider.ts +195 -0
  72. package/backend/src/providers/oauth/facebook.provider.ts +194 -0
  73. package/backend/src/providers/oauth/github.provider.ts +208 -0
  74. package/backend/src/providers/oauth/google.provider.ts +249 -0
  75. package/backend/src/providers/oauth/index.ts +7 -0
  76. package/backend/src/providers/oauth/linkedin.provider.ts +240 -0
  77. package/backend/src/providers/oauth/microsoft.provider.ts +169 -0
  78. package/backend/src/providers/oauth/x.provider.ts +202 -0
  79. package/backend/src/providers/storage/base.provider.ts +29 -0
  80. package/backend/src/providers/storage/local.provider.ts +103 -0
  81. package/backend/src/providers/storage/s3.provider.ts +313 -0
  82. package/backend/src/server.ts +70 -74
  83. package/backend/src/{core/ai/config.ts → services/ai/ai-config.service.ts} +19 -24
  84. package/backend/src/services/ai/ai-model.service.ts +60 -0
  85. package/backend/src/{core/ai/usage.ts → services/ai/ai-usage.service.ts} +28 -35
  86. package/backend/src/{core/ai/chat.ts → services/ai/chat-completion.service.ts} +37 -24
  87. package/backend/src/services/ai/helpers.ts +64 -0
  88. package/backend/src/{core/ai/image.ts → services/ai/image-generation.service.ts} +17 -19
  89. package/backend/src/services/ai/index.ts +13 -0
  90. package/backend/src/services/auth/auth-config.service.ts +250 -0
  91. package/backend/src/services/auth/auth-otp.service.ts +424 -0
  92. package/backend/src/services/auth/auth.service.ts +1136 -0
  93. package/backend/src/services/auth/index.ts +4 -0
  94. package/backend/src/{core/auth/oauth.ts → services/auth/oauth-config.service.ts} +106 -52
  95. package/backend/src/{core/database/advance.ts → services/database/database-advance.service.ts} +97 -131
  96. package/backend/src/services/database/database-table.service.ts +811 -0
  97. package/backend/src/services/email/email.service.ts +75 -0
  98. package/backend/src/{core/functions/functions.ts → services/functions/function.service.ts} +95 -88
  99. package/backend/src/{core/logs/audit.ts → services/logs/audit.service.ts} +92 -75
  100. package/backend/src/services/logs/log.service.ts +73 -0
  101. package/backend/src/{core/secrets/secrets.ts → services/secrets/secret.service.ts} +48 -66
  102. package/backend/src/services/storage/storage.service.ts +617 -0
  103. package/backend/src/services/usage/usage.service.ts +149 -0
  104. package/backend/src/types/auth.ts +66 -2
  105. package/backend/src/types/email.ts +8 -0
  106. package/backend/src/types/error-constants.ts +4 -0
  107. package/backend/src/types/logs.ts +0 -29
  108. package/backend/src/{core/socket/types.ts → types/socket.ts} +5 -6
  109. package/backend/src/utils/environment.ts +9 -3
  110. package/backend/src/utils/logger.ts +20 -2
  111. package/backend/src/utils/seed.ts +150 -57
  112. package/backend/src/utils/sql-parser.ts +1 -1
  113. package/backend/src/utils/utils.ts +114 -0
  114. package/backend/src/utils/validations.ts +40 -4
  115. package/backend/tests/local/test-ai-config.sh +129 -0
  116. package/backend/tests/local/test-ai-usage.sh +80 -0
  117. package/backend/tests/local/test-auth-router.sh +1 -1
  118. package/backend/tests/local/test-e2e.sh +1 -1
  119. package/backend/tests/local/test-functions.sh +123 -0
  120. package/backend/tests/local/test-logs.sh +132 -0
  121. package/backend/tests/local/test-public-bucket.sh +3 -3
  122. package/backend/tests/local/test-secrets.sh +14 -12
  123. package/backend/tests/local/test-traditional-rest.sh +2 -2
  124. package/backend/tests/manual/test-rawsql-modes.sh +244 -0
  125. package/backend/tests/test-config.sh +37 -1
  126. package/backend/tests/unit/cloud-token.test.ts +48 -0
  127. package/backend/tests/unit/constant.test.ts +8 -0
  128. package/backend/tests/unit/email.test.ts +372 -0
  129. package/backend/tests/unit/environment.test.ts +59 -0
  130. package/backend/tests/unit/helpers.test.ts +63 -0
  131. package/backend/tests/unit/logger.test.ts +22 -0
  132. package/backend/tests/unit/rate-limit.test.ts +154 -0
  133. package/backend/tests/unit/response.test.ts +58 -0
  134. package/backend/tests/unit/sql-parser.test.ts +74 -0
  135. package/backend/tests/unit/uuid.test.ts +21 -0
  136. package/backend/tests/unit/validations.test.ts +80 -0
  137. package/backend/tsconfig.json +1 -1
  138. package/backend/vitest.config.ts +11 -0
  139. package/claude-plugin/.claude-plugin/plugin.json +24 -0
  140. package/claude-plugin/README.md +133 -0
  141. package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +270 -0
  142. package/docker-compose.prod.yml +60 -4
  143. package/docker-compose.yml +65 -4
  144. package/docker-init/db/db-init.sql +6 -34
  145. package/docker-init/logs/vector.yml +236 -0
  146. package/docs/README.md +44 -0
  147. package/docs/changelog.mdx +67 -0
  148. package/docs/core-concepts/ai/architecture.mdx +373 -0
  149. package/docs/core-concepts/ai/sdk.mdx +213 -0
  150. package/docs/core-concepts/authentication/architecture.mdx +278 -0
  151. package/docs/core-concepts/authentication/sdk.mdx +414 -0
  152. package/docs/core-concepts/authentication/ui-components/customization.mdx +529 -0
  153. package/docs/core-concepts/authentication/ui-components/nextjs.mdx +221 -0
  154. package/docs/core-concepts/authentication/ui-components/react-router.mdx +184 -0
  155. package/docs/core-concepts/authentication/ui-components/react.mdx +129 -0
  156. package/docs/core-concepts/database/architecture.mdx +256 -0
  157. package/docs/core-concepts/database/sdk.mdx +382 -0
  158. package/docs/core-concepts/functions/architecture.mdx +105 -0
  159. package/docs/core-concepts/functions/sdk.mdx +184 -0
  160. package/docs/core-concepts/storage/architecture.mdx +243 -0
  161. package/docs/core-concepts/storage/sdk.mdx +253 -0
  162. package/docs/deployment/README.md +94 -0
  163. package/docs/deployment/deploy-to-aws-ec2.md +565 -0
  164. package/docs/deployment/deploy-to-azure-virtual-machines.md +313 -0
  165. package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -0
  166. package/docs/deployment/deploy-to-render.md +441 -0
  167. package/docs/docs.json +210 -0
  168. package/docs/examples/framework-guides/nextjs.mdx +131 -0
  169. package/docs/examples/framework-guides/nuxt.mdx +165 -0
  170. package/docs/examples/framework-guides/react.mdx +165 -0
  171. package/docs/examples/framework-guides/svelte.mdx +153 -0
  172. package/docs/examples/framework-guides/vue.mdx +159 -0
  173. package/docs/examples/overview.mdx +67 -0
  174. package/docs/favicon.svg +19 -0
  175. package/docs/images/changelog/nov-2025/auth-components.webp +0 -0
  176. package/docs/images/changelog/nov-2025/database-metadata.webp +0 -0
  177. package/docs/images/changelog/nov-2025/quickstart-prompts.webp +0 -0
  178. package/docs/images/changelog/nov-2025/sql-editor.webp +0 -0
  179. package/docs/images/changelog/nov-2025/usage-page.webp +0 -0
  180. package/docs/images/changelog/october-2025/csv-upload.webp +0 -0
  181. package/docs/images/changelog/october-2025/logs-feature.webp +0 -0
  182. package/docs/images/changelog/october-2025/oauth-providers.webp +0 -0
  183. package/docs/images/checks-passed.png +0 -0
  184. package/docs/images/dashboard-connect-expanded.png +0 -0
  185. package/docs/images/dashboard-connect.png +0 -0
  186. package/docs/images/hero-dark.png +0 -0
  187. package/docs/images/hero-light.png +0 -0
  188. package/docs/images/icons/ai.svg +4 -0
  189. package/docs/images/icons/auth.svg +1 -0
  190. package/docs/images/icons/database.svg +1 -0
  191. package/docs/images/icons/function.svg +1 -0
  192. package/docs/images/icons/storage.svg +1 -0
  193. package/docs/images/logos/nextjs.svg +4 -0
  194. package/docs/images/logos/nuxt.svg +4 -0
  195. package/docs/images/logos/react.svg +5 -0
  196. package/docs/images/logos/svelte.svg +4 -0
  197. package/docs/images/logos/vue.svg +5 -0
  198. package/docs/images/mcp-install.png +0 -0
  199. package/docs/images/onboarding-mcp.png +0 -0
  200. package/docs/insforge-instructions-sdk.md +55 -374
  201. package/docs/introduction.mdx +45 -0
  202. package/docs/logo/dark.svg +22 -0
  203. package/docs/logo/light.svg +20 -0
  204. package/docs/partnership.mdx +647 -0
  205. package/docs/quickstart.mdx +83 -0
  206. package/docs/showcase/2048-arena.png +0 -0
  207. package/docs/showcase/framegen-cloud.png +0 -0
  208. package/docs/showcase/line-connect-race.png +0 -0
  209. package/docs/showcase/moment-vibe.png +0 -0
  210. package/docs/showcase/national-flags.png +0 -0
  211. package/docs/showcase/pokemon-vibe.png +0 -0
  212. package/docs/showcase/pure-browse-buy.png +0 -0
  213. package/docs/showcase.mdx +52 -0
  214. package/docs/snippets/sdk-installation.mdx +22 -0
  215. package/docs/snippets/service-icons.mdx +27 -0
  216. package/eslint.config.js +10 -3
  217. package/frontend/package.json +10 -4
  218. package/frontend/src/App.tsx +13 -82
  219. package/frontend/src/assets/icons/connected.svg +3 -0
  220. package/frontend/src/assets/icons/loader.svg +9 -0
  221. package/frontend/src/assets/logos/apple.svg +4 -0
  222. package/frontend/src/assets/logos/discord.svg +1 -1
  223. package/frontend/src/assets/logos/facebook.svg +3 -0
  224. package/frontend/src/assets/logos/instagram.svg +2 -0
  225. package/frontend/src/assets/logos/linkedin.svg +3 -0
  226. package/frontend/src/assets/logos/microsoft.svg +1 -0
  227. package/frontend/src/assets/logos/spotify.svg +17 -0
  228. package/frontend/src/assets/logos/tiktok.svg +6 -0
  229. package/frontend/src/assets/logos/x.svg +3 -0
  230. package/frontend/src/components/Checkbox.tsx +27 -29
  231. package/frontend/src/components/CodeBlock.tsx +55 -2
  232. package/frontend/src/components/CodeEditor.tsx +92 -0
  233. package/frontend/src/components/ConfirmDialog.tsx +1 -1
  234. package/frontend/src/components/ConnectCTA.tsx +38 -0
  235. package/frontend/src/components/CopyButton.tsx +52 -15
  236. package/frontend/src/components/ErrorState.tsx +1 -2
  237. package/frontend/src/components/FeatureSidebar.tsx +6 -6
  238. package/frontend/src/components/FeatureSidebarItem.tsx +2 -2
  239. package/frontend/src/components/JsonHighlight.tsx +21 -9
  240. package/frontend/src/components/ProjectInfoModal.tsx +128 -0
  241. package/frontend/src/components/PromptDialog.tsx +1 -4
  242. package/frontend/src/components/SearchInput.tsx +1 -2
  243. package/frontend/src/components/Stepper.tsx +53 -0
  244. package/frontend/src/components/ThemeToggle.tsx +3 -3
  245. package/frontend/src/components/datagrid/DataGrid.tsx +25 -32
  246. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +1 -2
  247. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +2 -4
  248. package/frontend/src/components/datagrid/index.ts +23 -0
  249. package/frontend/src/components/index.ts +23 -30
  250. package/frontend/src/components/layout/AppHeader.tsx +133 -92
  251. package/frontend/src/components/layout/AppSidebar.tsx +80 -170
  252. package/frontend/src/components/layout/Layout.tsx +12 -23
  253. package/frontend/src/components/layout/PrimaryMenu.tsx +187 -0
  254. package/frontend/src/components/layout/SecondaryMenu.tsx +70 -0
  255. package/frontend/src/components/layout/index.ts +5 -0
  256. package/frontend/src/components/radix/Tooltip.tsx +24 -13
  257. package/frontend/src/components/radix/index.ts +22 -0
  258. package/frontend/src/features/ai/components/AIConfigCard.tsx +129 -83
  259. package/frontend/src/features/ai/components/AIEmptyState.tsx +12 -7
  260. package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +101 -0
  261. package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -0
  262. package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -0
  263. package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -0
  264. package/frontend/src/features/ai/components/index.ts +6 -0
  265. package/frontend/src/features/ai/helpers.ts +57 -71
  266. package/frontend/src/features/ai/hooks/useAIConfigs.ts +39 -113
  267. package/frontend/src/features/ai/hooks/useAIUsage.ts +0 -2
  268. package/frontend/src/features/ai/page/AIPage.tsx +67 -79
  269. package/frontend/src/features/ai/services/ai.service.ts +5 -5
  270. package/frontend/src/features/auth/components/AuthPreview.tsx +96 -0
  271. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +53 -30
  272. package/frontend/src/features/auth/components/UserFormDialog.tsx +13 -6
  273. package/frontend/src/features/auth/components/UsersDataGrid.tsx +44 -14
  274. package/frontend/src/features/auth/components/index.ts +5 -0
  275. package/frontend/src/features/auth/helpers.tsx +200 -0
  276. package/frontend/src/features/auth/hooks/useAnonToken.ts +30 -0
  277. package/frontend/src/features/auth/hooks/useAuthConfig.ts +48 -0
  278. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +14 -10
  279. package/frontend/src/features/auth/hooks/useUsers.ts +43 -5
  280. package/frontend/src/features/auth/index.ts +3 -2
  281. package/frontend/src/features/auth/page/AuthMethodsPage.tsx +275 -0
  282. package/frontend/src/features/auth/page/ConfigurationPage.tsx +395 -0
  283. package/frontend/src/features/auth/page/UsersPage.tsx +285 -0
  284. package/frontend/src/features/auth/services/anonToken.service.ts +11 -0
  285. package/frontend/src/features/auth/services/config.service.ts +19 -0
  286. package/frontend/src/features/auth/services/{oauth.service.ts → oauth-config.service.ts} +4 -4
  287. package/frontend/src/features/auth/services/{auth.service.ts → user.service.ts} +7 -53
  288. package/frontend/src/features/dashboard/components/ConnectionSuccessBanner.tsx +35 -0
  289. package/frontend/src/features/dashboard/components/PromptCard.tsx +21 -0
  290. package/frontend/src/features/dashboard/components/PromptDialog.tsx +103 -0
  291. package/frontend/src/features/dashboard/components/StatsCard.tsx +50 -0
  292. package/frontend/src/features/dashboard/components/index.ts +4 -0
  293. package/frontend/src/features/dashboard/page/DashboardPage.tsx +187 -169
  294. package/frontend/src/features/dashboard/prompts/ai-chatbot.ts +13 -0
  295. package/frontend/src/features/dashboard/prompts/crm-system.ts +13 -0
  296. package/frontend/src/features/dashboard/prompts/ecommerce-platform.ts +12 -0
  297. package/frontend/src/features/dashboard/prompts/index.ts +31 -0
  298. package/frontend/src/features/dashboard/prompts/instagram-clone.ts +11 -0
  299. package/frontend/src/features/dashboard/prompts/notion-clone.ts +14 -0
  300. package/frontend/src/features/dashboard/prompts/reddit-clone.ts +12 -0
  301. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +48 -17
  302. package/frontend/src/features/database/components/ForeignKeyCell.tsx +15 -34
  303. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +19 -20
  304. package/frontend/src/features/database/components/LinkRecordModal.tsx +120 -125
  305. package/frontend/src/features/database/components/RecordFormDialog.tsx +22 -33
  306. package/frontend/src/features/database/components/RecordFormField.tsx +45 -47
  307. package/frontend/src/features/database/components/TableEmptyState.tsx +6 -5
  308. package/frontend/src/features/database/components/TableForm.tsx +28 -15
  309. package/frontend/src/features/database/components/TableFormColumn.tsx +2 -3
  310. package/frontend/src/features/database/components/TableSidebar.tsx +1 -1
  311. package/frontend/src/features/database/components/TablesEmptyState.tsx +48 -0
  312. package/frontend/src/features/database/components/TemplateCard.tsx +37 -0
  313. package/frontend/src/features/database/components/TemplatePreview.tsx +92 -0
  314. package/frontend/src/features/database/components/index.ts +19 -0
  315. package/frontend/src/features/database/constants.ts +28 -2
  316. package/frontend/src/features/database/contexts/SQLEditorContext.tsx +188 -0
  317. package/frontend/src/features/database/helpers.ts +2 -2
  318. package/frontend/src/features/database/hooks/useCSVImport.ts +29 -0
  319. package/frontend/src/features/database/hooks/useFullMetadata.ts +18 -0
  320. package/frontend/src/features/database/hooks/useRawSQL.ts +55 -0
  321. package/frontend/src/features/database/hooks/useRecords.ts +139 -0
  322. package/frontend/src/features/database/hooks/useTables.ts +131 -0
  323. package/frontend/src/features/database/index.ts +6 -1
  324. package/frontend/src/features/database/page/FunctionsPage.tsx +211 -0
  325. package/frontend/src/features/database/page/IndexesPage.tsx +240 -0
  326. package/frontend/src/features/database/page/PoliciesPage.tsx +248 -0
  327. package/frontend/src/features/database/page/SQLEditorPage.tsx +382 -0
  328. package/frontend/src/features/database/page/{DatabasePage.tsx → TablesPage.tsx} +186 -185
  329. package/frontend/src/features/database/page/TemplatesPage.tsx +39 -0
  330. package/frontend/src/features/database/page/TriggersPage.tsx +242 -0
  331. package/frontend/src/features/database/services/advance.service.ts +66 -0
  332. package/frontend/src/features/database/services/{database.service.ts → record.service.ts} +67 -64
  333. package/frontend/src/features/database/services/table.service.ts +64 -0
  334. package/frontend/src/features/database/templates/ai-chatbot.ts +402 -0
  335. package/frontend/src/features/database/templates/crm-system.ts +528 -0
  336. package/frontend/src/features/database/templates/ecommerce-platform.ts +553 -0
  337. package/frontend/src/features/database/templates/index.ts +34 -0
  338. package/frontend/src/features/database/templates/instagram-clone.ts +222 -0
  339. package/frontend/src/features/database/templates/notion-clone.ts +483 -0
  340. package/frontend/src/features/database/templates/reddit-clone.ts +526 -0
  341. package/frontend/src/features/functions/components/FunctionRow.tsx +2 -1
  342. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +1 -1
  343. package/frontend/src/features/functions/components/SecretRow.tsx +1 -1
  344. package/frontend/src/features/functions/components/index.ts +5 -0
  345. package/frontend/src/features/functions/hooks/useFunctions.ts +4 -4
  346. package/frontend/src/features/{secrets → functions}/hooks/useSecrets.ts +5 -5
  347. package/frontend/src/features/functions/page/FunctionsPage.tsx +160 -17
  348. package/frontend/src/features/functions/{components/SecretsContent.tsx → page/SecretsPage.tsx} +8 -12
  349. package/frontend/src/features/functions/services/{functions.service.ts → function.service.ts} +2 -2
  350. package/frontend/src/features/{secrets/services/secrets.service.ts → functions/services/secret.service.ts} +2 -2
  351. package/frontend/src/features/login/hooks/usePartnerOrigin.ts +27 -0
  352. package/frontend/src/features/login/page/CloudLoginPage.tsx +79 -54
  353. package/frontend/src/features/login/page/LoginPage.tsx +16 -23
  354. package/frontend/src/features/login/services/partnership.service.ts +65 -0
  355. package/frontend/src/features/logs/components/LogsDataGrid.tsx +89 -0
  356. package/frontend/src/features/logs/components/SeverityBadge.tsx +18 -0
  357. package/frontend/src/features/logs/components/index.ts +2 -0
  358. package/frontend/src/features/logs/helpers.ts +24 -0
  359. package/frontend/src/features/logs/hooks/useAuditLogs.ts +4 -4
  360. package/frontend/src/features/logs/hooks/useLogSources.ts +137 -0
  361. package/frontend/src/features/logs/hooks/useLogs.ts +163 -0
  362. package/frontend/src/features/logs/hooks/useMcpUsage.ts +181 -0
  363. package/frontend/src/features/logs/index.ts +8 -2
  364. package/frontend/src/features/logs/page/AuditsPage.tsx +91 -38
  365. package/frontend/src/features/logs/page/LogsPage.tsx +152 -0
  366. package/frontend/src/features/logs/page/MCPLogsPage.tsx +84 -0
  367. package/frontend/src/features/logs/services/audit.service.ts +63 -0
  368. package/frontend/src/features/logs/services/log.service.ts +15 -110
  369. package/frontend/src/features/logs/services/usage.service.ts +31 -0
  370. package/frontend/src/features/onboard/components/McpConnectionStatus.tsx +68 -0
  371. package/frontend/src/features/onboard/components/OnboardingModal.tsx +267 -0
  372. package/frontend/src/features/onboard/components/VideoDemoModal.tsx +38 -0
  373. package/frontend/src/features/onboard/components/index.ts +4 -0
  374. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +2 -2
  375. package/frontend/src/features/onboard/components/mcp/{mcp-helper.tsx → helpers.tsx} +8 -8
  376. package/frontend/src/features/onboard/components/mcp/index.ts +2 -3
  377. package/frontend/src/features/onboard/index.ts +13 -3
  378. package/frontend/src/features/storage/components/BucketEmptyState.tsx +9 -6
  379. package/frontend/src/features/storage/components/BucketFormDialog.tsx +25 -41
  380. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +20 -8
  381. package/frontend/src/features/storage/components/StorageDataGrid.tsx +4 -3
  382. package/frontend/src/features/storage/components/StorageManager.tsx +23 -34
  383. package/frontend/src/features/storage/components/index.ts +12 -0
  384. package/frontend/src/features/storage/hooks/useStorage.ts +208 -0
  385. package/frontend/src/features/storage/page/StoragePage.tsx +41 -115
  386. package/frontend/src/features/storage/services/storage.service.ts +22 -1
  387. package/frontend/src/features/visualizer/components/AuthNode.tsx +72 -56
  388. package/frontend/src/features/visualizer/components/BucketNode.tsx +4 -4
  389. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +108 -80
  390. package/frontend/src/features/visualizer/components/TableNode.tsx +34 -41
  391. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +12 -4
  392. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +33 -29
  393. package/frontend/src/index.css +1 -0
  394. package/frontend/src/lib/analytics/posthog.tsx +27 -0
  395. package/frontend/src/lib/contexts/AuthContext.tsx +38 -31
  396. package/frontend/src/lib/contexts/SocketContext.tsx +5 -6
  397. package/frontend/src/{features/metadata → lib}/hooks/useMetadata.ts +1 -1
  398. package/frontend/src/lib/hooks/useToast.tsx +6 -2
  399. package/frontend/src/lib/routing/AppRoutes.tsx +84 -0
  400. package/frontend/src/lib/routing/RequireAuth.tsx +27 -0
  401. package/frontend/src/lib/utils/cloudMessaging.ts +20 -0
  402. package/frontend/src/lib/utils/menuItems.ts +183 -0
  403. package/frontend/src/lib/utils/{validation-schemas.ts → schemaValidations.ts} +10 -5
  404. package/frontend/src/lib/utils/utils.ts +19 -1
  405. package/frontend/src/vite-env.d.ts +1 -0
  406. package/frontend/vite.config.ts +5 -3
  407. package/functions/server.ts +28 -3
  408. package/functions/worker-template.js +15 -4
  409. package/i18n/README.ar.md +130 -0
  410. package/i18n/README.de.md +130 -0
  411. package/i18n/README.es.md +154 -0
  412. package/i18n/README.fr.md +134 -0
  413. package/i18n/README.hi.md +129 -0
  414. package/i18n/README.ja.md +174 -0
  415. package/i18n/README.ko.md +137 -0
  416. package/i18n/README.pt-BR.md +131 -0
  417. package/i18n/README.ru.md +129 -0
  418. package/i18n/README.zh-CN.md +133 -0
  419. package/openapi/ai.yaml +31 -4
  420. package/openapi/auth.yaml +827 -146
  421. package/package.json +16 -7
  422. package/shared-schemas/package.json +1 -1
  423. package/shared-schemas/src/ai-api.schema.ts +34 -58
  424. package/shared-schemas/src/ai.schema.ts +5 -0
  425. package/shared-schemas/src/auth-api.schema.ts +154 -8
  426. package/shared-schemas/src/auth.schema.ts +42 -6
  427. package/shared-schemas/src/cloud-events.schema.ts +57 -0
  428. package/shared-schemas/src/database-api.schema.ts +3 -3
  429. package/shared-schemas/src/database.schema.ts +1 -1
  430. package/shared-schemas/src/index.ts +1 -0
  431. package/shared-schemas/src/logs-api.schema.ts +7 -1
  432. package/shared-schemas/src/logs.schema.ts +26 -0
  433. package/shared-schemas/src/metadata.schema.ts +9 -4
  434. package/test-gemini.sh +35 -0
  435. package/test-usage-admin.sh +57 -0
  436. package/test-usage.sh +50 -0
  437. package/zeabur/README.md +13 -0
  438. package/zeabur/template.yml +1032 -0
  439. package/.github/workflows/deploy-aws.yml +0 -130
  440. package/backend/src/api/routes/agent.ts +0 -29
  441. package/backend/src/api/routes/auth.oauth.ts +0 -482
  442. package/backend/src/api/routes/auth.ts +0 -386
  443. package/backend/src/api/routes/docs.ts +0 -66
  444. package/backend/src/api/routes/functions.ts +0 -183
  445. package/backend/src/api/routes/openapi.ts +0 -82
  446. package/backend/src/api/routes/usage.ts +0 -96
  447. package/backend/src/core/ai/client.ts +0 -242
  448. package/backend/src/core/ai/model.ts +0 -117
  449. package/backend/src/core/auth/auth.ts +0 -781
  450. package/backend/src/core/database/table.ts +0 -772
  451. package/backend/src/core/documentation/agent.ts +0 -689
  452. package/backend/src/core/documentation/openapi.ts +0 -856
  453. package/backend/src/core/logs/analytics.ts +0 -76
  454. package/backend/src/core/logs/providers/localdb.provider.ts +0 -246
  455. package/backend/src/core/storage/storage.ts +0 -923
  456. package/backend/src/utils/cloud-token.ts +0 -39
  457. package/backend/src/utils/helpers.ts +0 -49
  458. package/backend/src/utils/uuid.ts +0 -9
  459. package/backend/tests/manual/test-better-auth.sh +0 -303
  460. package/docker-init/db/logs.sql +0 -9
  461. package/frontend/README.md +0 -112
  462. package/frontend/src/components/datagrid/index.tsx +0 -20
  463. package/frontend/src/components/layout/CloudLayout.tsx +0 -95
  464. package/frontend/src/features/ai/components/AIConfigDialog.tsx +0 -76
  465. package/frontend/src/features/ai/components/AIConfigForm.tsx +0 -222
  466. package/frontend/src/features/ai/components/fields/ModalityField.tsx +0 -87
  467. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +0 -134
  468. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +0 -33
  469. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +0 -106
  470. package/frontend/src/features/auth/components/AuthMethodTab.tsx +0 -238
  471. package/frontend/src/features/auth/components/UsersTab.tsx +0 -114
  472. package/frontend/src/features/auth/page/AuthenticationPage.tsx +0 -169
  473. package/frontend/src/features/database/hooks/UseLinkModal.tsx +0 -78
  474. package/frontend/src/features/functions/components/FunctionViewer.tsx +0 -46
  475. package/frontend/src/features/functions/components/FunctionsContent.tsx +0 -88
  476. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +0 -87
  477. package/frontend/src/features/login/components/PrivateRoute.tsx +0 -24
  478. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +0 -313
  479. package/frontend/src/features/logs/components/LogsTable.tsx +0 -199
  480. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +0 -530
  481. package/frontend/src/features/metadata/index.ts +0 -0
  482. package/frontend/src/features/metadata/page/MetadataPage.tsx +0 -136
  483. package/frontend/src/features/onboard/components/CompletionCard.tsx +0 -41
  484. package/frontend/src/features/onboard/components/OnboardButton.tsx +0 -84
  485. package/frontend/src/features/onboard/components/StepContent.tsx +0 -91
  486. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +0 -53
  487. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +0 -144
  488. package/frontend/src/features/onboard/page/OnBoardPage.tsx +0 -104
  489. package/frontend/src/features/onboard/types.ts +0 -8
  490. package/frontend/src/lib/contexts/OnboardStepContext.tsx +0 -68
  491. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +0 -29
  492. /package/backend/src/api/{middleware → middlewares}/error.ts +0 -0
  493. /package/backend/src/api/{middleware → middlewares}/upload.ts +0 -0
  494. /package/backend/{migrations → src/infra/database/migrations}/000_create-base-tables.sql +0 -0
  495. /package/backend/{migrations → src/infra/database/migrations}/001_create-helper-functions.sql +0 -0
  496. /package/backend/{migrations → src/infra/database/migrations}/002_rename-auth-tables.sql +0 -0
  497. /package/backend/{migrations → src/infra/database/migrations}/003_create-users-table.sql +0 -0
  498. /package/backend/{migrations → src/infra/database/migrations}/004_add-reload-postgrest-func.sql +0 -0
  499. /package/backend/{migrations → src/infra/database/migrations}/005_enable-project-admin-modify-users.sql +0 -0
  500. /package/backend/{migrations → src/infra/database/migrations}/006_modify-ai-usage-table.sql +0 -0
  501. /package/backend/{migrations → src/infra/database/migrations}/007_drop-metadata-table.sql +0 -0
  502. /package/backend/{migrations → src/infra/database/migrations}/008_add-system-tables.sql +0 -0
  503. /package/backend/{migrations → src/infra/database/migrations}/009_add-function-secrets.sql +0 -0
  504. /package/backend/{migrations → src/infra/database/migrations}/010_modify-ai-config-modalities.sql +0 -0
  505. /package/backend/{migrations → src/infra/database/migrations}/011_refactor-secrets-table.sql +0 -0
  506. /package/backend/{migrations → src/infra/database/migrations}/012_add-storage-uploaded-by.sql +0 -0
  507. /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
+ }