insforge 0.3.1

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 (395) hide show
  1. package/.dockerignore +58 -0
  2. package/.env.example +49 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
  4. package/.github/ISSUE_TEMPLATE/config.yml +11 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
  6. package/.github/copilot-instructions.md +147 -0
  7. package/.github/workflows/build-image.yml +65 -0
  8. package/.github/workflows/ci-premerge-check.yml +24 -0
  9. package/.github/workflows/deploy-aws.yml +130 -0
  10. package/.github/workflows/lint-and-format.yml +33 -0
  11. package/.prettierignore +65 -0
  12. package/.prettierrc +9 -0
  13. package/CHANGELOG.md +3 -0
  14. package/CONTRIBUTING.md +126 -0
  15. package/Dockerfile +27 -0
  16. package/GITHUB_OAUTH_SETUP.md +49 -0
  17. package/GOOGLE_OAUTH_SETUP.md +148 -0
  18. package/LICENSE +201 -0
  19. package/README.md +134 -0
  20. package/assets/Dark.svg +23 -0
  21. package/assets/archDiagram.png +0 -0
  22. package/assets/banner.png +0 -0
  23. package/assets/mcpInstallv2.png +0 -0
  24. package/assets/sampleResponse.png +0 -0
  25. package/assets/signin.png +0 -0
  26. package/assets/userflow.png +0 -0
  27. package/backend/migrations/000_create-base-tables.sql +142 -0
  28. package/backend/migrations/001_create-helper-functions.sql +41 -0
  29. package/backend/migrations/002_rename-auth-tables.sql +30 -0
  30. package/backend/migrations/003_create-users-table.sql +56 -0
  31. package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
  32. package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
  33. package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
  34. package/backend/migrations/007_drop-metadata-table.sql +2 -0
  35. package/backend/migrations/008_add-system-tables.sql +77 -0
  36. package/backend/migrations/009_add-function-secrets.sql +24 -0
  37. package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
  38. package/backend/migrations/011_refactor-secrets-table.sql +15 -0
  39. package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
  40. package/backend/package.json +75 -0
  41. package/backend/src/api/middleware/auth.ts +240 -0
  42. package/backend/src/api/middleware/error.ts +231 -0
  43. package/backend/src/api/middleware/upload.ts +59 -0
  44. package/backend/src/api/routes/agent.ts +29 -0
  45. package/backend/src/api/routes/ai.ts +472 -0
  46. package/backend/src/api/routes/auth.oauth.ts +482 -0
  47. package/backend/src/api/routes/auth.ts +386 -0
  48. package/backend/src/api/routes/database.advance.ts +275 -0
  49. package/backend/src/api/routes/database.records.ts +246 -0
  50. package/backend/src/api/routes/database.tables.ts +161 -0
  51. package/backend/src/api/routes/docs.ts +66 -0
  52. package/backend/src/api/routes/functions.ts +183 -0
  53. package/backend/src/api/routes/logs.ts +150 -0
  54. package/backend/src/api/routes/metadata.ts +160 -0
  55. package/backend/src/api/routes/openapi.ts +82 -0
  56. package/backend/src/api/routes/secrets.ts +199 -0
  57. package/backend/src/api/routes/storage.ts +547 -0
  58. package/backend/src/api/routes/usage.ts +96 -0
  59. package/backend/src/core/ai/chat.ts +207 -0
  60. package/backend/src/core/ai/client.ts +242 -0
  61. package/backend/src/core/ai/config.ts +187 -0
  62. package/backend/src/core/ai/image.ts +156 -0
  63. package/backend/src/core/ai/model.ts +117 -0
  64. package/backend/src/core/ai/usage.ts +290 -0
  65. package/backend/src/core/auth/auth.ts +781 -0
  66. package/backend/src/core/auth/oauth.ts +398 -0
  67. package/backend/src/core/database/advance.ts +1074 -0
  68. package/backend/src/core/database/manager.ts +178 -0
  69. package/backend/src/core/database/table.ts +772 -0
  70. package/backend/src/core/documentation/agent.ts +689 -0
  71. package/backend/src/core/documentation/openapi.ts +856 -0
  72. package/backend/src/core/functions/functions.ts +310 -0
  73. package/backend/src/core/logs/analytics.ts +76 -0
  74. package/backend/src/core/logs/audit.ts +255 -0
  75. package/backend/src/core/logs/providers/base.provider.ts +83 -0
  76. package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
  77. package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
  78. package/backend/src/core/secrets/encryption.ts +58 -0
  79. package/backend/src/core/secrets/secrets.ts +410 -0
  80. package/backend/src/core/socket/socket.ts +388 -0
  81. package/backend/src/core/socket/types.ts +79 -0
  82. package/backend/src/core/storage/storage.ts +923 -0
  83. package/backend/src/server.ts +288 -0
  84. package/backend/src/types/ai.ts +46 -0
  85. package/backend/src/types/auth.ts +90 -0
  86. package/backend/src/types/database.ts +136 -0
  87. package/backend/src/types/error-constants.ts +86 -0
  88. package/backend/src/types/logs.ts +47 -0
  89. package/backend/src/types/profile.ts +55 -0
  90. package/backend/src/types/storage.ts +23 -0
  91. package/backend/src/utils/cloud-token.ts +39 -0
  92. package/backend/src/utils/constants.ts +1 -0
  93. package/backend/src/utils/environment.ts +35 -0
  94. package/backend/src/utils/helpers.ts +49 -0
  95. package/backend/src/utils/logger.ts +13 -0
  96. package/backend/src/utils/response.ts +62 -0
  97. package/backend/src/utils/seed.ts +205 -0
  98. package/backend/src/utils/sql-parser.ts +63 -0
  99. package/backend/src/utils/uuid.ts +9 -0
  100. package/backend/src/utils/validations.ts +129 -0
  101. package/backend/tests/README.md +134 -0
  102. package/backend/tests/cleanup-all-test-data.sh +231 -0
  103. package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
  104. package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
  105. package/backend/tests/local/test-auth-router.sh +144 -0
  106. package/backend/tests/local/test-database-router.sh +222 -0
  107. package/backend/tests/local/test-e2e.sh +241 -0
  108. package/backend/tests/local/test-fk-errors.sh +97 -0
  109. package/backend/tests/local/test-id-field.sh +201 -0
  110. package/backend/tests/local/test-public-bucket.sh +265 -0
  111. package/backend/tests/local/test-secrets.sh +248 -0
  112. package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
  113. package/backend/tests/local/test-traditional-rest.sh +209 -0
  114. package/backend/tests/manual/README.md +51 -0
  115. package/backend/tests/manual/create-large-table-simple.sql +11 -0
  116. package/backend/tests/manual/seed-large-table.sql +101 -0
  117. package/backend/tests/manual/setup-large-table-extras.sql +34 -0
  118. package/backend/tests/manual/test-better-auth.sh +303 -0
  119. package/backend/tests/manual/test-bulk-upsert.sh +410 -0
  120. package/backend/tests/manual/test-database-advance.sh +297 -0
  121. package/backend/tests/manual/test-postgrest-stability.sh +192 -0
  122. package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
  123. package/backend/tests/manual/test-universal-storage.sh +264 -0
  124. package/backend/tests/manual/test-users.sql +18 -0
  125. package/backend/tests/run-all-tests.sh +140 -0
  126. package/backend/tests/setup.ts +22 -0
  127. package/backend/tests/test-config.sh +303 -0
  128. package/backend/tsconfig.json +23 -0
  129. package/backend/tsup.config.ts +18 -0
  130. package/backend/vitest.config.ts +22 -0
  131. package/docker-compose.prod.yml +145 -0
  132. package/docker-compose.yml +167 -0
  133. package/docker-init/db/db-init.sql +125 -0
  134. package/docker-init/db/jwt.sql +5 -0
  135. package/docker-init/db/logs.sql +9 -0
  136. package/docker-init/db/postgresql.conf +17 -0
  137. package/docs/deprecated/insforge-auth-api.md +215 -0
  138. package/docs/deprecated/insforge-auth-sdk.md +100 -0
  139. package/docs/deprecated/insforge-db-api.md +359 -0
  140. package/docs/deprecated/insforge-db-sdk.md +140 -0
  141. package/docs/deprecated/insforge-debug-sdk.md +157 -0
  142. package/docs/deprecated/insforge-debug.md +65 -0
  143. package/docs/deprecated/insforge-instructions.md +124 -0
  144. package/docs/deprecated/insforge-project.md +118 -0
  145. package/docs/deprecated/insforge-storage-api.md +279 -0
  146. package/docs/deprecated/insforge-storage-sdk.md +159 -0
  147. package/docs/insforge-instructions-sdk.md +407 -0
  148. package/eslint.config.js +317 -0
  149. package/examples/oauth/frontend-oauth-example.html +251 -0
  150. package/examples/response-examples.md +444 -0
  151. package/frontend/README.md +112 -0
  152. package/frontend/components.json +17 -0
  153. package/frontend/index.html +13 -0
  154. package/frontend/package.json +63 -0
  155. package/frontend/public/favicon.ico +0 -0
  156. package/frontend/src/App.tsx +106 -0
  157. package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
  158. package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
  159. package/frontend/src/assets/icons/checked.svg +3 -0
  160. package/frontend/src/assets/icons/error.svg +3 -0
  161. package/frontend/src/assets/icons/pencil.svg +4 -0
  162. package/frontend/src/assets/icons/refresh.svg +4 -0
  163. package/frontend/src/assets/icons/step_active.svg +3 -0
  164. package/frontend/src/assets/icons/step_inactive.svg +11 -0
  165. package/frontend/src/assets/icons/warning.svg +3 -0
  166. package/frontend/src/assets/logos/amazon.svg +1 -0
  167. package/frontend/src/assets/logos/claude_code.svg +3 -0
  168. package/frontend/src/assets/logos/cline.svg +6 -0
  169. package/frontend/src/assets/logos/cursor.svg +20 -0
  170. package/frontend/src/assets/logos/discord.svg +9 -0
  171. package/frontend/src/assets/logos/gemini.svg +19 -0
  172. package/frontend/src/assets/logos/github.svg +5 -0
  173. package/frontend/src/assets/logos/google.svg +13 -0
  174. package/frontend/src/assets/logos/grok.svg +10 -0
  175. package/frontend/src/assets/logos/insforge_dark.svg +15 -0
  176. package/frontend/src/assets/logos/insforge_light.svg +15 -0
  177. package/frontend/src/assets/logos/openai.svg +10 -0
  178. package/frontend/src/assets/logos/roo_code.svg +9 -0
  179. package/frontend/src/assets/logos/trae.svg +3 -0
  180. package/frontend/src/assets/logos/windsurf.svg +10 -0
  181. package/frontend/src/components/ButtonWithLoading.tsx +27 -0
  182. package/frontend/src/components/Checkbox.tsx +61 -0
  183. package/frontend/src/components/CodeBlock.tsx +32 -0
  184. package/frontend/src/components/ConfirmDialog.tsx +96 -0
  185. package/frontend/src/components/CopyButton.tsx +69 -0
  186. package/frontend/src/components/DeleteActionButton.tsx +42 -0
  187. package/frontend/src/components/EmptyState.tsx +41 -0
  188. package/frontend/src/components/ErrorState.tsx +35 -0
  189. package/frontend/src/components/FeatureSidebar.tsx +126 -0
  190. package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
  191. package/frontend/src/components/JsonHighlight.tsx +61 -0
  192. package/frontend/src/components/LoadingState.tsx +16 -0
  193. package/frontend/src/components/PaginationControls.tsx +54 -0
  194. package/frontend/src/components/PromptDialog.tsx +68 -0
  195. package/frontend/src/components/SearchInput.tsx +90 -0
  196. package/frontend/src/components/SelectionClearButton.tsx +26 -0
  197. package/frontend/src/components/Stepper.tsx +139 -0
  198. package/frontend/src/components/ThemeToggle.tsx +58 -0
  199. package/frontend/src/components/TypeBadge.tsx +20 -0
  200. package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
  201. package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
  202. package/frontend/src/components/datagrid/IdCell.tsx +44 -0
  203. package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
  204. package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
  205. package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
  206. package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
  207. package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
  208. package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
  209. package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
  210. package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
  211. package/frontend/src/components/datagrid/index.tsx +20 -0
  212. package/frontend/src/components/index.ts +39 -0
  213. package/frontend/src/components/layout/AppHeader.tsx +146 -0
  214. package/frontend/src/components/layout/AppSidebar.tsx +190 -0
  215. package/frontend/src/components/layout/CloudLayout.tsx +95 -0
  216. package/frontend/src/components/layout/Layout.tsx +43 -0
  217. package/frontend/src/components/radix/Alert.tsx +45 -0
  218. package/frontend/src/components/radix/AlertDialog.tsx +115 -0
  219. package/frontend/src/components/radix/Avatar.tsx +45 -0
  220. package/frontend/src/components/radix/Badge.tsx +33 -0
  221. package/frontend/src/components/radix/Button.tsx +50 -0
  222. package/frontend/src/components/radix/Card.tsx +58 -0
  223. package/frontend/src/components/radix/Dialog.tsx +98 -0
  224. package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
  225. package/frontend/src/components/radix/Form.tsx +167 -0
  226. package/frontend/src/components/radix/Input.tsx +22 -0
  227. package/frontend/src/components/radix/Label.tsx +19 -0
  228. package/frontend/src/components/radix/Popover.tsx +29 -0
  229. package/frontend/src/components/radix/ScrollArea.tsx +44 -0
  230. package/frontend/src/components/radix/Select.tsx +151 -0
  231. package/frontend/src/components/radix/Separator.tsx +26 -0
  232. package/frontend/src/components/radix/Sheet.tsx +119 -0
  233. package/frontend/src/components/radix/Skeleton.tsx +7 -0
  234. package/frontend/src/components/radix/Switch.tsx +29 -0
  235. package/frontend/src/components/radix/Tabs.tsx +50 -0
  236. package/frontend/src/components/radix/Textarea.tsx +21 -0
  237. package/frontend/src/components/radix/Tooltip.tsx +28 -0
  238. package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
  239. package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
  240. package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
  241. package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
  242. package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
  243. package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
  244. package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
  245. package/frontend/src/features/ai/helpers.ts +155 -0
  246. package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
  247. package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
  248. package/frontend/src/features/ai/page/AIPage.tsx +178 -0
  249. package/frontend/src/features/ai/services/ai.service.ts +148 -0
  250. package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
  251. package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
  252. package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
  253. package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
  254. package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
  255. package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
  256. package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
  257. package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
  258. package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
  259. package/frontend/src/features/auth/index.ts +9 -0
  260. package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
  261. package/frontend/src/features/auth/services/auth.service.ts +112 -0
  262. package/frontend/src/features/auth/services/oauth.service.ts +49 -0
  263. package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
  264. package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
  265. package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
  266. package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
  267. package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
  268. package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
  269. package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
  270. package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
  271. package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
  272. package/frontend/src/features/database/components/TableForm.tsx +656 -0
  273. package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
  274. package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
  275. package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
  276. package/frontend/src/features/database/constants.ts +26 -0
  277. package/frontend/src/features/database/helpers.ts +125 -0
  278. package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
  279. package/frontend/src/features/database/index.ts +12 -0
  280. package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
  281. package/frontend/src/features/database/schema.ts +25 -0
  282. package/frontend/src/features/database/services/database.service.ts +216 -0
  283. package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
  284. package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
  285. package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
  286. package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
  287. package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
  288. package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
  289. package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
  290. package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
  291. package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
  292. package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
  293. package/frontend/src/features/functions/services/functions.service.ts +48 -0
  294. package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
  295. package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
  296. package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
  297. package/frontend/src/features/login/page/LoginPage.tsx +174 -0
  298. package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
  299. package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
  300. package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
  301. package/frontend/src/features/logs/index.ts +5 -0
  302. package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
  303. package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
  304. package/frontend/src/features/logs/services/log.service.ts +171 -0
  305. package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
  306. package/frontend/src/features/metadata/index.ts +0 -0
  307. package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
  308. package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
  309. package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
  310. package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
  311. package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
  312. package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
  313. package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
  314. package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
  315. package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
  316. package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
  317. package/frontend/src/features/onboard/index.ts +3 -0
  318. package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
  319. package/frontend/src/features/onboard/types.ts +8 -0
  320. package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
  321. package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
  322. package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
  323. package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
  324. package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
  325. package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
  326. package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
  327. package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
  328. package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
  329. package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
  330. package/frontend/src/features/storage/index.ts +3 -0
  331. package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
  332. package/frontend/src/features/storage/services/storage.service.ts +144 -0
  333. package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
  334. package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
  335. package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
  336. package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
  337. package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
  338. package/frontend/src/features/visualizer/components/index.ts +5 -0
  339. package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
  340. package/frontend/src/index.css +248 -0
  341. package/frontend/src/lib/api/client.ts +163 -0
  342. package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
  343. package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
  344. package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
  345. package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
  346. package/frontend/src/lib/hooks/useAuth.ts +4 -0
  347. package/frontend/src/lib/hooks/useConfirm.ts +55 -0
  348. package/frontend/src/lib/hooks/useInterval.ts +27 -0
  349. package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
  350. package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
  351. package/frontend/src/lib/hooks/usePagination.ts +27 -0
  352. package/frontend/src/lib/hooks/useTimeout.ts +27 -0
  353. package/frontend/src/lib/hooks/useToast.tsx +229 -0
  354. package/frontend/src/lib/utils/constants.ts +38 -0
  355. package/frontend/src/lib/utils/utils.ts +165 -0
  356. package/frontend/src/lib/utils/validation-schemas.ts +126 -0
  357. package/frontend/src/main.tsx +16 -0
  358. package/frontend/src/rdg.css +194 -0
  359. package/frontend/src/vite-env.d.ts +12 -0
  360. package/frontend/tailwind.config.js +97 -0
  361. package/frontend/tsconfig.json +26 -0
  362. package/frontend/tsconfig.node.json +10 -0
  363. package/frontend/vite.config.ts +37 -0
  364. package/frontend/vitest.config.ts +36 -0
  365. package/functions/deno.json +25 -0
  366. package/functions/server.ts +290 -0
  367. package/functions/worker-template.js +126 -0
  368. package/openapi/ai.yaml +689 -0
  369. package/openapi/auth.yaml +563 -0
  370. package/openapi/functions.yaml +476 -0
  371. package/openapi/health.yaml +30 -0
  372. package/openapi/logs.yaml +224 -0
  373. package/openapi/metadata.yaml +178 -0
  374. package/openapi/records.yaml +382 -0
  375. package/openapi/secrets.yaml +371 -0
  376. package/openapi/storage.yaml +876 -0
  377. package/openapi/tables.yaml +464 -0
  378. package/package.json +88 -0
  379. package/shared-schemas/package.json +31 -0
  380. package/shared-schemas/src/ai-api.schema.ts +167 -0
  381. package/shared-schemas/src/ai.schema.ts +54 -0
  382. package/shared-schemas/src/auth-api.schema.ts +193 -0
  383. package/shared-schemas/src/auth.schema.ts +94 -0
  384. package/shared-schemas/src/database-api.schema.ts +259 -0
  385. package/shared-schemas/src/database.schema.ts +69 -0
  386. package/shared-schemas/src/functions-api.schema.ts +25 -0
  387. package/shared-schemas/src/functions.schema.ts +16 -0
  388. package/shared-schemas/src/index.ts +13 -0
  389. package/shared-schemas/src/logs-api.schema.ts +49 -0
  390. package/shared-schemas/src/logs.schema.ts +14 -0
  391. package/shared-schemas/src/metadata.schema.ts +56 -0
  392. package/shared-schemas/src/storage-api.schema.ts +65 -0
  393. package/shared-schemas/src/storage.schema.ts +19 -0
  394. package/shared-schemas/tsconfig.json +21 -0
  395. package/tsconfig.json +8 -0
@@ -0,0 +1,547 @@
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
+ import path from 'path';
3
+ import { verifyAdmin, AuthRequest, verifyUser } from '@/api/middleware/auth.js';
4
+ import { AppError } from '@/api/middleware/error.js';
5
+ import { StorageService } from '@/core/storage/storage.js';
6
+ import { DatabaseManager } from '@/core/database/manager.js';
7
+ import { successResponse } from '@/utils/response.js';
8
+ import { upload, handleUploadError } from '@/api/middleware/upload.js';
9
+ import { ERROR_CODES } from '@/types/error-constants.js';
10
+ import {
11
+ StorageBucketSchema,
12
+ createBucketRequestSchema,
13
+ updateBucketRequestSchema,
14
+ } from '@insforge/shared-schemas';
15
+ import { SocketService } from '@/core/socket/socket';
16
+ import { DataUpdateResourceType, ServerEvents } from '@/core/socket/types';
17
+ import { AuditService } from '@/core/logs/audit.js';
18
+
19
+ const router = Router();
20
+ const auditService = AuditService.getInstance();
21
+
22
+ // Middleware to conditionally apply authentication based on bucket visibility
23
+ const conditionalAuth = async (req: Request, res: Response, next: NextFunction) => {
24
+ // For GET and HEAD requests to download objects, check if bucket is public
25
+ if ((req.method === 'GET' || req.method === 'HEAD') && req.params.bucketName) {
26
+ try {
27
+ const storageService = StorageService.getInstance();
28
+ const isPublic = await storageService.isBucketPublic(req.params.bucketName);
29
+
30
+ if (isPublic) {
31
+ // Public bucket - skip authentication
32
+ return next();
33
+ }
34
+ } catch {
35
+ // If error checking bucket, continue with auth requirement
36
+ }
37
+ }
38
+
39
+ // All other cases require authentication
40
+ return verifyUser(req, res, next);
41
+ };
42
+
43
+ // GET /api/storage/buckets - List all buckets (requires auth)
44
+ router.get('/buckets', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
45
+ try {
46
+ const db = DatabaseManager.getInstance().getDb();
47
+
48
+ // Get all buckets with their metadata from _storage_buckets table
49
+ const buckets = (await db
50
+ .prepare('SELECT name, public, created_at FROM _storage_buckets ORDER BY name')
51
+ .all()) as StorageBucketSchema[];
52
+
53
+ // Traditional REST: return array directly
54
+ successResponse(res, buckets);
55
+ } catch (error) {
56
+ next(error);
57
+ }
58
+ });
59
+
60
+ // POST /api/storage/buckets - Create a new bucket (requires auth)
61
+ router.post(
62
+ '/buckets',
63
+ verifyAdmin,
64
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
65
+ try {
66
+ const validation = createBucketRequestSchema.safeParse(req.body);
67
+ if (!validation.success) {
68
+ throw new AppError(
69
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
70
+ 400,
71
+ ERROR_CODES.STORAGE_INVALID_PARAMETER,
72
+ 'Please check the request body, it must conform with the CreateBucketRequest schema.'
73
+ );
74
+ }
75
+ const { bucketName, isPublic } = validation.data;
76
+
77
+ const storageService = StorageService.getInstance();
78
+ await storageService.createBucket(bucketName, isPublic);
79
+
80
+ // Log audit for bucket creation
81
+ await auditService.log({
82
+ actor: req.user?.email || 'api-key',
83
+ action: 'CREATE_BUCKET',
84
+ module: 'STORAGE',
85
+ details: {
86
+ bucketName,
87
+ isPublic,
88
+ },
89
+ ip_address: req.ip,
90
+ });
91
+
92
+ const socket = SocketService.getInstance();
93
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
94
+ resource: DataUpdateResourceType.STORAGE_SCHEMA,
95
+ });
96
+
97
+ const accessInfo = isPublic
98
+ ? 'This is a PUBLIC bucket - objects can be accessed without authentication.'
99
+ : 'This is a PRIVATE bucket - authentication is required to access objects.';
100
+
101
+ successResponse(
102
+ res,
103
+ {
104
+ message: 'Bucket created successfully',
105
+ bucketName,
106
+ isPublic: isPublic,
107
+ nextActions: `${accessInfo} You can use /api/storage/buckets/:bucketName/objects/:objectKey to upload an object to the bucket, and /api/storage/buckets/:bucketName/objects to list the objects in the bucket.`,
108
+ },
109
+ 201
110
+ );
111
+ } catch (error) {
112
+ if (error instanceof Error && error.message.includes('already exists')) {
113
+ next(new AppError(error.message, 409, ERROR_CODES.ALREADY_EXISTS));
114
+ } else if (error instanceof Error && error.message.includes('Invalid bucket name')) {
115
+ next(
116
+ new AppError(
117
+ error.message,
118
+ 400,
119
+ ERROR_CODES.STORAGE_INVALID_PARAMETER,
120
+ 'Please check the bucket name, it must be a valid bucket name'
121
+ )
122
+ );
123
+ } else {
124
+ next(error);
125
+ }
126
+ }
127
+ }
128
+ );
129
+
130
+ // PATCH /api/storage/buckets/:bucketName - Update bucket (requires auth)
131
+ router.patch(
132
+ '/buckets/:bucketName',
133
+ verifyAdmin,
134
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
135
+ try {
136
+ const { bucketName } = req.params;
137
+ const validation = updateBucketRequestSchema.safeParse(req.body);
138
+ if (!validation.success) {
139
+ throw new AppError(
140
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
141
+ 400,
142
+ ERROR_CODES.STORAGE_INVALID_PARAMETER,
143
+ 'Please check the request body, it must conform with the UpdateBucketRequest schema.'
144
+ );
145
+ }
146
+ const { isPublic } = validation.data;
147
+
148
+ const storageService = StorageService.getInstance();
149
+ await storageService.updateBucketVisibility(bucketName, isPublic);
150
+
151
+ // Log audit for bucket update
152
+ await auditService.log({
153
+ actor: req.user?.email || 'api-key',
154
+ action: 'UPDATE_BUCKET',
155
+ module: 'STORAGE',
156
+ details: {
157
+ bucketName,
158
+ isPublic,
159
+ },
160
+ ip_address: req.ip,
161
+ });
162
+
163
+ const socket = SocketService.getInstance();
164
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
165
+ resource: DataUpdateResourceType.BUCKET_SCHEMA,
166
+ data: {
167
+ name: bucketName,
168
+ },
169
+ });
170
+
171
+ const accessInfo = isPublic
172
+ ? 'Bucket is now PUBLIC - objects can be accessed without authentication.'
173
+ : 'Bucket is now PRIVATE - authentication is required to access objects.';
174
+
175
+ successResponse(
176
+ res,
177
+ {
178
+ message: 'Bucket visibility updated',
179
+ bucket: bucketName,
180
+ isPublic: isPublic,
181
+ nextActions: accessInfo,
182
+ },
183
+ 200
184
+ );
185
+ } catch (error) {
186
+ if (error instanceof Error && error.message.includes('does not exist')) {
187
+ next(new AppError(error.message, 404, ERROR_CODES.NOT_FOUND));
188
+ } else {
189
+ next(error);
190
+ }
191
+ }
192
+ }
193
+ );
194
+
195
+ // GET /api/storage/buckets/:bucketName/objects - List objects in bucket (requires auth)
196
+ router.get(
197
+ '/buckets/:bucketName/objects',
198
+ verifyAdmin,
199
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
200
+ try {
201
+ const { bucketName } = req.params;
202
+ const prefix = req.query.prefix as string;
203
+ const searchQuery = req.query.search as string;
204
+ const limit = Math.min(parseInt(req.query.limit as string) || 100, 1000);
205
+ const offset = parseInt(req.query.offset as string) || 0;
206
+
207
+ const storageService = StorageService.getInstance();
208
+ const result = await storageService.listObjects(
209
+ bucketName,
210
+ prefix,
211
+ limit,
212
+ offset,
213
+ searchQuery
214
+ );
215
+
216
+ successResponse(
217
+ res,
218
+ {
219
+ data: result.objects,
220
+ pagination: {
221
+ offset: offset,
222
+ limit: limit,
223
+ total: result.total,
224
+ },
225
+ nextActions:
226
+ 'You can use PUT /api/storage/buckets/:bucketName/objects/:objectKey to upload with a specific key, or POST /api/storage/buckets/:bucketName/objects to upload with auto-generated key, and GET /api/storage/buckets/:bucketName/objects/:objectKey to download an object.',
227
+ },
228
+ 200
229
+ );
230
+ } catch (error) {
231
+ next(error);
232
+ }
233
+ }
234
+ );
235
+
236
+ // PUT /api/storage/buckets/:bucketName/objects/:objectKey - Upload object to bucket (requires auth)
237
+ router.put(
238
+ '/buckets/:bucketName/objects/*',
239
+ verifyUser,
240
+ upload.single('file'),
241
+ handleUploadError,
242
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
243
+ try {
244
+ const { bucketName } = req.params;
245
+ const objectKey = req.params[0]; // Everything after objects
246
+
247
+ if (!objectKey) {
248
+ throw new AppError('Object key is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
249
+ }
250
+
251
+ if (!req.file) {
252
+ throw new AppError('File is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
253
+ }
254
+
255
+ const storageService = StorageService.getInstance();
256
+ const storedFile = await storageService.putObject(
257
+ bucketName,
258
+ objectKey,
259
+ req.file,
260
+ req.user?.id
261
+ );
262
+
263
+ successResponse(res, storedFile, 201);
264
+ } catch (error) {
265
+ if (error instanceof Error && error.message.includes('already exists')) {
266
+ next(new AppError(error.message, 409, ERROR_CODES.ALREADY_EXISTS));
267
+ } else if (error instanceof Error && error.message.includes('Invalid')) {
268
+ next(new AppError(error.message, 400, ERROR_CODES.STORAGE_INVALID_PARAMETER));
269
+ } else {
270
+ next(error);
271
+ }
272
+ }
273
+ }
274
+ );
275
+
276
+ // POST /api/storage/buckets/:bucketName/objects - Upload object with server-generated key (requires auth)
277
+ router.post(
278
+ '/buckets/:bucketName/objects',
279
+ verifyUser,
280
+ upload.single('file'),
281
+ handleUploadError,
282
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
283
+ try {
284
+ const { bucketName } = req.params;
285
+
286
+ if (!req.file) {
287
+ throw new AppError('File is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
288
+ }
289
+
290
+ // Generate a unique key for the object
291
+ const timestamp = Date.now();
292
+ const randomStr = Math.random().toString(36).substring(2, 8);
293
+ const fileExt = req.file.originalname ? path.extname(req.file.originalname) : '';
294
+ const baseName = req.file.originalname
295
+ ? path.basename(req.file.originalname, fileExt)
296
+ : 'file';
297
+ const sanitizedBaseName = baseName.replace(/[^a-zA-Z0-9-_]/g, '-').substring(0, 32);
298
+ const objectKey = `${sanitizedBaseName}-${timestamp}-${randomStr}${fileExt}`;
299
+
300
+ const storageService = StorageService.getInstance();
301
+ const storedFile = await storageService.putObject(
302
+ bucketName,
303
+ objectKey,
304
+ req.file,
305
+ req.user?.id
306
+ );
307
+
308
+ successResponse(res, storedFile, 201);
309
+ } catch (error) {
310
+ if (error instanceof Error && error.message.includes('does not exist')) {
311
+ next(
312
+ new AppError(
313
+ 'Bucket does not exist',
314
+ 404,
315
+ ERROR_CODES.NOT_FOUND,
316
+ 'Create the bucket first using POST /api/storage/buckets'
317
+ )
318
+ );
319
+ } else if (error instanceof Error && error.message.includes('Invalid')) {
320
+ next(new AppError(error.message, 400, ERROR_CODES.STORAGE_INVALID_PARAMETER));
321
+ } else {
322
+ next(error);
323
+ }
324
+ }
325
+ }
326
+ );
327
+
328
+ // GET /api/storage/buckets/:bucketName/objects/:objectKey - Download object from bucket (conditional auth)
329
+ router.get(
330
+ '/buckets/:bucketName/objects/*',
331
+ conditionalAuth,
332
+ async (req: AuthRequest | Request, res: Response, next: NextFunction) => {
333
+ try {
334
+ const { bucketName } = req.params;
335
+ const objectKey = req.params[0]; // Everything after objects
336
+
337
+ if (!objectKey) {
338
+ throw new AppError('Object key is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
339
+ }
340
+
341
+ const storageService = StorageService.getInstance();
342
+ const expiresIn = (await storageService.isBucketPublic(bucketName)) ? 0 : 3600;
343
+ const strategy = await storageService.getDownloadStrategy(
344
+ bucketName,
345
+ objectKey,
346
+ Number(expiresIn)
347
+ );
348
+ if (strategy.method === 'presigned') {
349
+ return res.redirect(strategy.url);
350
+ }
351
+
352
+ const result = await storageService.getObject(bucketName, objectKey);
353
+ if (!result) {
354
+ throw new AppError('Object not found', 404, ERROR_CODES.NOT_FOUND);
355
+ }
356
+
357
+ const { file, metadata } = result;
358
+
359
+ // Set appropriate headers
360
+ res.setHeader('Content-Type', metadata.mimeType || 'application/octet-stream');
361
+ res.setHeader('Content-Length', file.length.toString());
362
+
363
+ // Send object content
364
+ res.send(file);
365
+ } catch (error) {
366
+ if (error instanceof Error && error.message.includes('Invalid')) {
367
+ next(new AppError(error.message, 400, ERROR_CODES.STORAGE_INVALID_PARAMETER));
368
+ } else {
369
+ next(error);
370
+ }
371
+ }
372
+ }
373
+ );
374
+
375
+ // DELETE /api/storage/buckets/:bucketName - Delete entire bucket (requires auth)
376
+ router.delete(
377
+ '/buckets/:bucketName',
378
+ verifyAdmin,
379
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
380
+ try {
381
+ const { bucketName } = req.params;
382
+ const storageService = StorageService.getInstance();
383
+ const deleted = await storageService.deleteBucket(bucketName);
384
+
385
+ if (!deleted) {
386
+ throw new AppError('Bucket not found or already empty', 404, ERROR_CODES.NOT_FOUND);
387
+ }
388
+
389
+ // Log audit for bucket deletion
390
+ await auditService.log({
391
+ actor: req.user?.email || 'api-key',
392
+ action: 'DELETE_BUCKET',
393
+ module: 'STORAGE',
394
+ details: {
395
+ bucketName,
396
+ },
397
+ ip_address: req.ip,
398
+ });
399
+
400
+ const socket = SocketService.getInstance();
401
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
402
+ resource: DataUpdateResourceType.STORAGE_SCHEMA,
403
+ });
404
+
405
+ successResponse(
406
+ res,
407
+ {
408
+ message: 'Bucket deleted successfully',
409
+ nextActions:
410
+ 'You can use POST /api/storage/buckets to create a new bucket, and GET /api/storage/buckets/:bucketName/objects to list the objects in the bucket.',
411
+ },
412
+ 200
413
+ );
414
+ } catch (error) {
415
+ next(error);
416
+ }
417
+ }
418
+ );
419
+
420
+ // DELETE /api/storage/buckets/:bucketName/objects/:objectKey - Delete object from bucket (requires auth)
421
+ router.delete(
422
+ '/buckets/:bucketName/objects/*',
423
+ verifyUser,
424
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
425
+ try {
426
+ const { bucketName } = req.params;
427
+ const objectKey = req.params[0]; // Everything after objects
428
+
429
+ if (!objectKey) {
430
+ throw new AppError('Object key is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
431
+ }
432
+
433
+ // Delete specific object
434
+ const storageService = StorageService.getInstance();
435
+ const deleted = await storageService.deleteObject(bucketName, objectKey, req.user?.id);
436
+
437
+ if (!deleted) {
438
+ throw new AppError('Object not found', 404, ERROR_CODES.NOT_FOUND);
439
+ }
440
+
441
+ successResponse(res, { message: 'Object deleted successfully' });
442
+ } catch (error) {
443
+ if (error instanceof Error && error.message.includes('Invalid')) {
444
+ next(new AppError(error.message, 400, ERROR_CODES.STORAGE_INVALID_PARAMETER));
445
+ } else {
446
+ next(error);
447
+ }
448
+ }
449
+ }
450
+ );
451
+
452
+ // POST /api/storage/buckets/:bucketName/upload-strategy - Get upload strategy (presigned or direct)
453
+ router.post(
454
+ '/buckets/:bucketName/upload-strategy',
455
+ verifyUser,
456
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
457
+ try {
458
+ const { bucketName } = req.params;
459
+ const { filename, contentType, size } = req.body;
460
+
461
+ if (!filename) {
462
+ throw new AppError('Filename is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
463
+ }
464
+
465
+ const storageService = StorageService.getInstance();
466
+ const strategy = await storageService.getUploadStrategy(bucketName, {
467
+ filename,
468
+ contentType,
469
+ size,
470
+ });
471
+
472
+ successResponse(res, strategy);
473
+ } catch (error) {
474
+ if (error instanceof Error && error.message.includes('does not exist')) {
475
+ next(new AppError(error.message, 404, ERROR_CODES.NOT_FOUND));
476
+ } else {
477
+ next(error);
478
+ }
479
+ }
480
+ }
481
+ );
482
+
483
+ // POST /api/storage/buckets/:bucketName/objects/:objectKey/confirm-upload - Confirm presigned upload
484
+ router.post(
485
+ '/buckets/:bucketName/objects/:objectKey/confirm-upload',
486
+ verifyUser,
487
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
488
+ try {
489
+ const { bucketName, objectKey } = req.params;
490
+ const { size, contentType, etag } = req.body;
491
+
492
+ if (!size) {
493
+ throw new AppError('Size is required', 400, ERROR_CODES.STORAGE_INVALID_PARAMETER);
494
+ }
495
+
496
+ const storageService = StorageService.getInstance();
497
+ const fileInfo = await storageService.confirmUpload(
498
+ bucketName,
499
+ objectKey,
500
+ {
501
+ size,
502
+ contentType,
503
+ etag,
504
+ },
505
+ req.user?.id
506
+ );
507
+
508
+ successResponse(res, fileInfo, 201);
509
+ } catch (error) {
510
+ if (error instanceof Error && error.message.includes('not found')) {
511
+ next(new AppError(error.message, 404, ERROR_CODES.NOT_FOUND));
512
+ } else if (error instanceof Error && error.message.includes('already confirmed')) {
513
+ next(new AppError(error.message, 409, ERROR_CODES.ALREADY_EXISTS));
514
+ } else {
515
+ next(error);
516
+ }
517
+ }
518
+ }
519
+ );
520
+
521
+ // POST /api/storage/buckets/:bucketName/objects/:objectKey/download-strategy - Get download URL (presigned or direct)
522
+ router.post(
523
+ '/buckets/:bucketName/objects/:objectKey/download-strategy',
524
+ conditionalAuth,
525
+ async (req: AuthRequest | Request, res: Response, next: NextFunction) => {
526
+ try {
527
+ const { bucketName, objectKey } = req.params;
528
+ const { expiresIn = 3600 } = req.body;
529
+
530
+ const storageService = StorageService.getInstance();
531
+ const strategy = await storageService.getDownloadStrategy(
532
+ bucketName,
533
+ objectKey,
534
+ Number(expiresIn)
535
+ );
536
+
537
+ successResponse(res, strategy);
538
+ } catch (error) {
539
+ if (error instanceof Error && error.message.includes('Invalid')) {
540
+ next(new AppError(error.message, 400, ERROR_CODES.STORAGE_INVALID_PARAMETER));
541
+ } else {
542
+ next(error);
543
+ }
544
+ }
545
+ }
546
+ );
547
+ export { router as storageRouter };
@@ -0,0 +1,96 @@
1
+ import { Router } from 'express';
2
+ import { DatabaseManager } from '@/core/database/manager.js';
3
+ import { verifyApiKey, verifyCloudBackend } from '@/api/middleware/auth.js';
4
+
5
+ export const usageRouter = Router();
6
+
7
+ // Create MCP tool usage record
8
+ usageRouter.post('/mcp', verifyApiKey, async (req, res, next) => {
9
+ try {
10
+ const { tool_name, success = true } = req.body;
11
+
12
+ if (!tool_name) {
13
+ return res.status(400).json({
14
+ error: 'VALIDATION_ERROR',
15
+ message: 'tool_name is required',
16
+ });
17
+ }
18
+
19
+ const dbManager = DatabaseManager.getInstance();
20
+ const db = dbManager.getDb();
21
+
22
+ // Insert MCP usage record directly
23
+ await db
24
+ .prepare(
25
+ `
26
+ INSERT INTO _mcp_usage (tool_name, success)
27
+ VALUES ($1, $2)
28
+ `
29
+ )
30
+ .run(tool_name, success);
31
+
32
+ res.json({ success: true });
33
+ } catch (error) {
34
+ next(error);
35
+ }
36
+ });
37
+
38
+ // Get usage statistics (called by cloud backend)
39
+ usageRouter.get('/stats', verifyCloudBackend, async (req, res, next) => {
40
+ try {
41
+ const { start_date, end_date } = req.query;
42
+
43
+ if (!start_date || !end_date) {
44
+ return res.status(400).json({
45
+ error: 'VALIDATION_ERROR',
46
+ message: 'start_date and end_date are required',
47
+ });
48
+ }
49
+
50
+ const dbManager = DatabaseManager.getInstance();
51
+ const db = dbManager.getDb();
52
+
53
+ // Get MCP tool usage count within date range
54
+ const mcpResult = await db
55
+ .prepare(
56
+ `
57
+ SELECT COUNT(*) as count
58
+ FROM _mcp_usage
59
+ WHERE success = true
60
+ AND created_at >= $1
61
+ AND created_at < $2
62
+ `
63
+ )
64
+ .get(new Date(start_date as string), new Date(end_date as string));
65
+ const mcpUsageCount = parseInt(mcpResult?.count || '0');
66
+
67
+ // Get database size (in bytes)
68
+ const dbSizeResult = await db
69
+ .prepare(
70
+ `
71
+ SELECT pg_database_size(current_database()) as size
72
+ `
73
+ )
74
+ .get();
75
+ const databaseSize = parseInt(dbSizeResult?.size || '0');
76
+
77
+ // Get total storage size from _storage table
78
+ const storageResult = await db
79
+ .prepare(
80
+ `
81
+ SELECT COALESCE(SUM(size), 0) as total_size
82
+ FROM _storage
83
+ `
84
+ )
85
+ .get();
86
+ const storageSize = parseInt(storageResult?.total_size || '0');
87
+
88
+ res.json({
89
+ mcp_usage_count: mcpUsageCount,
90
+ database_size_bytes: databaseSize,
91
+ storage_size_bytes: storageSize,
92
+ });
93
+ } catch (error) {
94
+ next(error);
95
+ }
96
+ });