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,246 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import axios from 'axios';
3
+ import http from 'http';
4
+ import https from 'https';
5
+ import { AuthRequest, extractApiKey } from '@/api/middleware/auth.js';
6
+ import { DatabaseManager } from '@/core/database/manager.js';
7
+ import { AppError } from '@/api/middleware/error.js';
8
+ import { ERROR_CODES } from '@/types/error-constants.js';
9
+ import { validateTableName } from '@/utils/validations.js';
10
+ import { DatabaseRecord } from '@/types/database.js';
11
+ import { successResponse } from '@/utils/response.js';
12
+ import logger from '@/utils/logger.js';
13
+ import { SecretsService } from '@/core/secrets/secrets.js';
14
+ import { AuthService } from '@/core/auth/auth.js';
15
+
16
+ const router = Router();
17
+ const authService = AuthService.getInstance();
18
+ const secretService = new SecretsService();
19
+ const postgrestUrl = process.env.POSTGREST_BASE_URL || 'http://localhost:5430';
20
+
21
+ // Create a dedicated HTTP agent with connection pooling for PostgREST
22
+ // Optimized connection pool for Docker network communication
23
+ const httpAgent = new http.Agent({
24
+ keepAlive: true,
25
+ keepAliveMsecs: 5000, // Shorter for Docker network
26
+ maxSockets: 20, // Reduced for stability
27
+ maxFreeSockets: 5,
28
+ timeout: 10000, // Match axios timeout
29
+ });
30
+
31
+ const httpsAgent = new https.Agent({
32
+ keepAlive: true,
33
+ keepAliveMsecs: 5000,
34
+ maxSockets: 20,
35
+ maxFreeSockets: 5,
36
+ timeout: 10000,
37
+ });
38
+
39
+ // Create axios instance with optimized configuration for PostgREST
40
+ const postgrestAxios = axios.create({
41
+ httpAgent,
42
+ httpsAgent,
43
+ timeout: 10000, // Increased timeout for stability
44
+ maxRedirects: 0,
45
+ // Additional connection stability options
46
+ headers: {
47
+ Connection: 'keep-alive',
48
+ 'Keep-Alive': 'timeout=5, max=10',
49
+ },
50
+ });
51
+
52
+ // Generate admin token once and reuse
53
+ // If user request with api key, this token should be added automatically.
54
+ const adminToken = authService.generateToken({
55
+ sub: 'project-admin-with-api-key',
56
+ email: 'project-admin@email.com',
57
+ role: 'project_admin',
58
+ });
59
+
60
+ // anonymous users can access the database, postgREST does not require authentication, however we seed to unwrap session token for better auth, thus
61
+ // we need to verify user token below.
62
+ // router.use(verifyUserOrApiKey);
63
+
64
+ /**
65
+ * Forward database requests to PostgREST
66
+ */
67
+ const forwardToPostgrest = async (req: AuthRequest, res: Response, next: NextFunction) => {
68
+ const { tableName } = req.params;
69
+ const wildcardPath = req.params[0] || '';
70
+
71
+ // Build the target URL early so it's available in error handling
72
+ const targetPath = wildcardPath ? `/${tableName}/${wildcardPath}` : `/${tableName}`;
73
+ const targetUrl = `${postgrestUrl}${targetPath}`;
74
+
75
+ try {
76
+ // Validate table name with operation type
77
+ const method = req.method.toUpperCase();
78
+
79
+ try {
80
+ validateTableName(tableName);
81
+ } catch (error) {
82
+ if (error instanceof AppError) {
83
+ throw error;
84
+ }
85
+ throw new AppError('Invalid table name', 400, ERROR_CODES.INVALID_INPUT);
86
+ }
87
+
88
+ // Process request body for POST/PATCH/PUT operations
89
+ if (['POST', 'PATCH', 'PUT'].includes(method) && req.body && typeof req.body === 'object') {
90
+ const columnTypeMap = await DatabaseManager.getColumnTypeMap(tableName);
91
+ if (Array.isArray(req.body)) {
92
+ req.body = req.body.map((item) => {
93
+ if (item && typeof item === 'object') {
94
+ const filtered: DatabaseRecord = {};
95
+ for (const key in item) {
96
+ if (columnTypeMap[key] !== 'text' && item[key] === '') {
97
+ continue;
98
+ }
99
+ filtered[key] = item[key];
100
+ }
101
+ return filtered;
102
+ }
103
+ return item;
104
+ });
105
+ } else {
106
+ const body = req.body as DatabaseRecord;
107
+ for (const key in body) {
108
+ if (columnTypeMap[key] === 'uuid' && body[key] === '') {
109
+ delete body[key];
110
+ }
111
+ }
112
+ }
113
+ }
114
+
115
+ // Forward the request
116
+ const axiosConfig: {
117
+ method: string;
118
+ url: string;
119
+ params: unknown;
120
+ headers: Record<string, string | string[] | undefined>;
121
+ data?: unknown;
122
+ } = {
123
+ method: req.method,
124
+ url: targetUrl,
125
+ params: req.query,
126
+ headers: {
127
+ ...req.headers,
128
+ host: undefined, // Remove host header
129
+ 'content-length': undefined, // Let axios calculate
130
+ },
131
+ };
132
+
133
+ // Check for API key using shared logic
134
+ const apiKey = extractApiKey(req);
135
+
136
+ // If we have an API key, verify it and use admin token for PostgREST
137
+ if (apiKey) {
138
+ const isValid = await secretService.verifyApiKey(apiKey);
139
+ if (isValid) {
140
+ axiosConfig.headers.authorization = `Bearer ${adminToken}`;
141
+ }
142
+ }
143
+
144
+ // Add body for methods that support it
145
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
146
+ axiosConfig.data = req.body;
147
+ }
148
+
149
+ // Enhanced retry logic with improved error handling
150
+ let response;
151
+ let lastError;
152
+ const maxRetries = 3; // Increased retries for connection resets
153
+
154
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
155
+ try {
156
+ response = await postgrestAxios(axiosConfig);
157
+ break; // Success, exit retry loop
158
+ } catch (error) {
159
+ lastError = error;
160
+
161
+ // Retry on network errors (ECONNRESET, ECONNREFUSED, timeout) but not HTTP errors
162
+ const shouldRetry = axios.isAxiosError(error) && !error.response && attempt < maxRetries;
163
+
164
+ if (shouldRetry) {
165
+ logger.warn(`PostgREST request failed, retrying (attempt ${attempt}/${maxRetries})`, {
166
+ url: targetUrl,
167
+ errorCode: error.code,
168
+ message: error.message,
169
+ });
170
+
171
+ // Enhanced exponential backoff: 200ms, 500ms, 1000ms
172
+ const backoffDelay = Math.min(200 * Math.pow(2.5, attempt - 1), 1000);
173
+ await new Promise((resolve) => setTimeout(resolve, backoffDelay));
174
+ } else {
175
+ throw error; // Don't retry on HTTP errors or last attempt
176
+ }
177
+ }
178
+ }
179
+
180
+ if (!response) {
181
+ throw lastError || new Error('Failed to get response from PostgREST');
182
+ }
183
+
184
+ // Forward response headers
185
+ Object.entries(response.headers).forEach(([key, value]) => {
186
+ const keyLower = key.toLowerCase();
187
+ if (
188
+ keyLower !== 'content-length' &&
189
+ keyLower !== 'transfer-encoding' &&
190
+ keyLower !== 'connection' &&
191
+ keyLower !== 'content-encoding'
192
+ ) {
193
+ res.setHeader(key, value);
194
+ }
195
+ });
196
+
197
+ // Handle empty responses
198
+ let responseData = response.data;
199
+ if (
200
+ response.data === undefined ||
201
+ (typeof response.data === 'string' && response.data.trim() === '')
202
+ ) {
203
+ responseData = [];
204
+ }
205
+
206
+ successResponse(res, responseData, response.status);
207
+ } catch (error) {
208
+ if (axios.isAxiosError(error)) {
209
+ // Log more detailed error information
210
+ logger.error('PostgREST request failed', {
211
+ url: targetUrl,
212
+ method: req.method,
213
+ error: {
214
+ code: error.code,
215
+ message: error.message,
216
+ response: error.response?.data,
217
+ responseStatus: error.response?.status,
218
+ },
219
+ });
220
+
221
+ // Forward PostgREST errors
222
+ if (error.response) {
223
+ res.status(error.response.status).json(error.response.data);
224
+ } else {
225
+ // Network error - connection refused, DNS failure, etc.
226
+ const errorMessage =
227
+ error.code === 'ECONNREFUSED'
228
+ ? 'PostgREST connection refused'
229
+ : error.code === 'ENOTFOUND'
230
+ ? 'PostgREST service not found'
231
+ : 'Database service unavailable';
232
+
233
+ next(new AppError(errorMessage, 503, ERROR_CODES.INTERNAL_ERROR));
234
+ }
235
+ } else {
236
+ logger.error('Unexpected error in database route', { error });
237
+ next(error);
238
+ }
239
+ }
240
+ };
241
+
242
+ // Forward all database operations to PostgREST
243
+ router.all('/:tableName', forwardToPostgrest);
244
+ router.all('/:tableName/*', forwardToPostgrest);
245
+
246
+ export { router as databaseRecordsRouter };
@@ -0,0 +1,161 @@
1
+ import { Router, Response, NextFunction } from 'express';
2
+ import { verifyAdmin, verifyUser, AuthRequest } from '@/api/middleware/auth.js';
3
+ import { DatabaseTableService } from '@/core/database/table.js';
4
+ import { successResponse } from '@/utils/response.js';
5
+ import { AppError } from '@/api/middleware/error.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+ import { createTableRequestSchema, updateTableSchemaRequestSchema } from '@insforge/shared-schemas';
8
+ import { SocketService } from '@/core/socket/socket';
9
+ import { DataUpdateResourceType, ServerEvents } from '@/core/socket/types';
10
+ import { AuditService } from '@/core/logs/audit';
11
+
12
+ const router = Router();
13
+ const tableService = new DatabaseTableService();
14
+ const auditService = AuditService.getInstance();
15
+
16
+ // All table routes accept either JWT token or API key authentication
17
+ // router.use(verifyAdmin);
18
+
19
+ // List all tables
20
+ router.get('/', verifyUser, async (_req: AuthRequest, res: Response, next: NextFunction) => {
21
+ try {
22
+ const tables = await tableService.listTables();
23
+ successResponse(res, tables);
24
+ } catch (error) {
25
+ next(error);
26
+ }
27
+ });
28
+
29
+ // Create a new table
30
+ router.post('/', verifyAdmin, async (req: AuthRequest, res: Response, next: NextFunction) => {
31
+ try {
32
+ const validation = createTableRequestSchema.safeParse(req.body);
33
+ if (!validation.success) {
34
+ throw new AppError(
35
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
36
+ 400,
37
+ ERROR_CODES.INVALID_INPUT,
38
+ 'Please check the request body, it must conform with the CreateTableRequest schema.'
39
+ );
40
+ }
41
+
42
+ const { tableName, columns, rlsEnabled } = validation.data;
43
+ const result = await tableService.createTable(tableName, columns, rlsEnabled);
44
+
45
+ // Log audit for table creation
46
+ await auditService.log({
47
+ actor: req.user?.email || 'api-key',
48
+ action: 'CREATE_TABLE',
49
+ module: 'DATABASE',
50
+ details: {
51
+ tableName,
52
+ columns,
53
+ rlsEnabled,
54
+ },
55
+ ip_address: req.ip,
56
+ });
57
+
58
+ const socket = SocketService.getInstance();
59
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
60
+ resource: DataUpdateResourceType.DATABASE_SCHEMA,
61
+ });
62
+ successResponse(res, result, 201);
63
+ } catch (error) {
64
+ next(error);
65
+ }
66
+ });
67
+
68
+ // Get table schema
69
+ router.get(
70
+ '/:tableName/schema',
71
+ verifyAdmin,
72
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
73
+ try {
74
+ const { tableName } = req.params;
75
+ const schema = await tableService.getTableSchema(tableName);
76
+ successResponse(res, schema);
77
+ } catch (error) {
78
+ next(error);
79
+ }
80
+ }
81
+ );
82
+
83
+ // Update table schema
84
+ router.patch(
85
+ '/:tableName/schema',
86
+ verifyAdmin,
87
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
88
+ try {
89
+ const { tableName } = req.params;
90
+
91
+ const validation = updateTableSchemaRequestSchema.safeParse(req.body);
92
+ if (!validation.success) {
93
+ throw new AppError(
94
+ validation.error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '),
95
+ 400,
96
+ ERROR_CODES.INVALID_INPUT,
97
+ 'Please check the request body, it must conform with the UpdateTableRequest schema.'
98
+ );
99
+ }
100
+
101
+ const operations = validation.data;
102
+ const result = await tableService.updateTableSchema(tableName, operations);
103
+
104
+ // Log audit for table schema update
105
+ await auditService.log({
106
+ actor: req.user?.email || 'api-key',
107
+ action: 'UPDATE_TABLE',
108
+ module: 'DATABASE',
109
+ details: {
110
+ tableName,
111
+ operations,
112
+ },
113
+ ip_address: req.ip,
114
+ });
115
+
116
+ const socket = SocketService.getInstance();
117
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
118
+ resource: DataUpdateResourceType.TABLE_SCHEMA,
119
+ data: {
120
+ name: tableName,
121
+ },
122
+ });
123
+ successResponse(res, result);
124
+ } catch (error) {
125
+ next(error);
126
+ }
127
+ }
128
+ );
129
+
130
+ // Delete a table
131
+ router.delete(
132
+ '/:tableName',
133
+ verifyAdmin,
134
+ async (req: AuthRequest, res: Response, next: NextFunction) => {
135
+ try {
136
+ const { tableName } = req.params;
137
+ const result = await tableService.deleteTable(tableName);
138
+
139
+ // Log audit for table deletion
140
+ await auditService.log({
141
+ actor: req.user?.email || 'api-key',
142
+ action: 'DELETE_TABLE',
143
+ module: 'DATABASE',
144
+ details: {
145
+ tableName,
146
+ },
147
+ ip_address: req.ip,
148
+ });
149
+
150
+ const socket = SocketService.getInstance();
151
+ socket.broadcastToRoom('role:project_admin', ServerEvents.DATA_UPDATE, {
152
+ resource: DataUpdateResourceType.DATABASE_SCHEMA,
153
+ });
154
+ successResponse(res, result);
155
+ } catch (error) {
156
+ next(error);
157
+ }
158
+ }
159
+ );
160
+
161
+ export { router as databaseTablesRouter };
@@ -0,0 +1,66 @@
1
+ import { Router } from 'express';
2
+ import { readFile } from 'fs/promises';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { successResponse, errorResponse } from '@/utils/response.js';
6
+ import { ERROR_CODES } from '@/types/error-constants.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ const router = Router();
12
+
13
+ // Define available documentation files
14
+ const DOCS_MAP: Record<string, string> = {
15
+ instructions: 'insforge-instructions-sdk.md',
16
+ 'db-api': 'insforge-db-sdk.md',
17
+ 'auth-api': 'insforge-auth-sdk.md',
18
+ 'storage-api': 'insforge-storage-sdk.md',
19
+ debug: 'insforge-debug-sdk.md',
20
+ project: 'insforge-project-sdk.md',
21
+ };
22
+
23
+ // GET /api/docs/:docType - Get specific documentation
24
+ router.get('/:docType', async (req, res, next) => {
25
+ try {
26
+ const { docType } = req.params;
27
+
28
+ // Validate doc type
29
+ const docFileName = DOCS_MAP[docType];
30
+ if (!docFileName) {
31
+ return errorResponse(res, ERROR_CODES.NOT_FOUND, 'Documentation not found', 404);
32
+ }
33
+
34
+ // Read the documentation file
35
+ // PROJECT_ROOT is set in the docker-compose.yml file to point to the InsForge directory
36
+ const projectRoot = process.env.PROJECT_ROOT || path.resolve(__dirname, '../../../..');
37
+ const filePath = path.join(projectRoot, 'docs', docFileName);
38
+ const content = await readFile(filePath, 'utf-8');
39
+
40
+ // Traditional REST: return documentation directly
41
+ return successResponse(res, {
42
+ type: docType,
43
+ content,
44
+ });
45
+ } catch (error) {
46
+ // If file doesn't exist or other error
47
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
48
+ return errorResponse(res, ERROR_CODES.NOT_FOUND, 'Documentation file not found', 404);
49
+ }
50
+ next(error);
51
+ }
52
+ });
53
+
54
+ // GET /api/docs - List available documentation
55
+ router.get('/', (_req, res) => {
56
+ const available = Object.keys(DOCS_MAP).map((key) => ({
57
+ type: key,
58
+ filename: DOCS_MAP[key],
59
+ endpoint: `/api/docs/${key}`,
60
+ }));
61
+
62
+ // Traditional REST: return list directly
63
+ return successResponse(res, available);
64
+ });
65
+
66
+ export { router as docsRouter };
@@ -0,0 +1,183 @@
1
+ import { Router, Response } from 'express';
2
+ import { AuthRequest, verifyAdmin } from '@/api/middleware/auth.js';
3
+ import { FunctionsService } from '@/core/functions/functions.js';
4
+ import { AuditService } from '@/core/logs/audit.js';
5
+ import { AppError } from '@/api/middleware/error.js';
6
+ import logger from '@/utils/logger.js';
7
+ import { functionUploadRequestSchema, functionUpdateRequestSchema } from '@insforge/shared-schemas';
8
+
9
+ const router = Router();
10
+ const functionsService = FunctionsService.getInstance();
11
+ const auditService = AuditService.getInstance();
12
+
13
+ /**
14
+ * GET /api/functions
15
+ * List all edge functions
16
+ */
17
+ router.get('/', verifyAdmin, async (req: AuthRequest, res: Response) => {
18
+ try {
19
+ const result = await functionsService.listFunctions();
20
+ res.json(result);
21
+ } catch {
22
+ res.status(500).json({ error: 'Failed to list functions' });
23
+ }
24
+ });
25
+
26
+ /**
27
+ * GET /api/functions/:slug
28
+ * Get specific function details including code
29
+ */
30
+ router.get('/:slug', verifyAdmin, async (req: AuthRequest, res: Response) => {
31
+ try {
32
+ const { slug } = req.params;
33
+ const func = await functionsService.getFunction(slug);
34
+
35
+ if (!func) {
36
+ return res.status(404).json({ error: 'Function not found' });
37
+ }
38
+
39
+ res.json(func);
40
+ } catch {
41
+ res.status(500).json({ error: 'Failed to get function' });
42
+ }
43
+ });
44
+
45
+ /**
46
+ * POST /api/functions
47
+ * Create a new function
48
+ */
49
+ router.post('/', verifyAdmin, async (req: AuthRequest, res: Response) => {
50
+ try {
51
+ const validation = functionUploadRequestSchema.safeParse(req.body);
52
+ if (!validation.success) {
53
+ return res.status(400).json({
54
+ error: 'Invalid request',
55
+ details: validation.error.issues,
56
+ });
57
+ }
58
+
59
+ const created = await functionsService.createFunction(validation.data);
60
+
61
+ // Log audit event
62
+ logger.info(`Function ${created.name} (${created.slug}) created by ${req.user?.email}`);
63
+ await auditService.log({
64
+ actor: req.user?.email || 'api-key',
65
+ action: 'CREATE_FUNCTION',
66
+ module: 'FUNCTIONS',
67
+ details: {
68
+ functionId: created.id,
69
+ slug: created.slug,
70
+ name: created.name,
71
+ status: created.status,
72
+ },
73
+ ip_address: req.ip,
74
+ });
75
+
76
+ res.status(201).json({
77
+ success: true,
78
+ function: created,
79
+ });
80
+ } catch (error) {
81
+ if (error instanceof AppError) {
82
+ return res.status(error.statusCode).json({
83
+ error: error.code,
84
+ message: error.message,
85
+ });
86
+ }
87
+
88
+ res.status(500).json({
89
+ error: 'INTERNAL_ERROR',
90
+ message: error instanceof Error ? error.message : 'Failed to create function',
91
+ });
92
+ }
93
+ });
94
+
95
+ /**
96
+ * PUT /api/functions/:slug
97
+ * Update an existing function
98
+ */
99
+ router.put('/:slug', verifyAdmin, async (req: AuthRequest, res: Response) => {
100
+ try {
101
+ const { slug } = req.params;
102
+ const validation = functionUpdateRequestSchema.safeParse(req.body);
103
+
104
+ if (!validation.success) {
105
+ return res.status(400).json({
106
+ error: 'Invalid request',
107
+ details: validation.error.issues,
108
+ });
109
+ }
110
+
111
+ const updated = await functionsService.updateFunction(slug, validation.data);
112
+
113
+ if (!updated) {
114
+ return res.status(404).json({ error: 'Function not found' });
115
+ }
116
+
117
+ // Log audit event
118
+ logger.info(`Function ${slug} updated by ${req.user?.email}`);
119
+ await auditService.log({
120
+ actor: req.user?.email || 'api-key',
121
+ action: 'UPDATE_FUNCTION',
122
+ module: 'FUNCTIONS',
123
+ details: {
124
+ slug,
125
+ changes: validation.data,
126
+ },
127
+ ip_address: req.ip,
128
+ });
129
+
130
+ res.json({
131
+ success: true,
132
+ function: updated,
133
+ });
134
+ } catch (error) {
135
+ if (error instanceof AppError) {
136
+ return res.status(error.statusCode).json({
137
+ error: error.code,
138
+ message: error.message,
139
+ });
140
+ }
141
+
142
+ res.status(500).json({
143
+ error: 'INTERNAL_ERROR',
144
+ message: error instanceof Error ? error.message : 'Failed to update function',
145
+ });
146
+ }
147
+ });
148
+
149
+ /**
150
+ * DELETE /api/functions/:slug
151
+ * Delete a function
152
+ */
153
+ router.delete('/:slug', verifyAdmin, async (req: AuthRequest, res: Response) => {
154
+ try {
155
+ const { slug } = req.params;
156
+ const deleted = await functionsService.deleteFunction(slug);
157
+
158
+ if (!deleted) {
159
+ return res.status(404).json({ error: 'Function not found' });
160
+ }
161
+
162
+ // Log audit event
163
+ logger.info(`Function ${slug} deleted by ${req.user?.email}`);
164
+ await auditService.log({
165
+ actor: req.user?.email || 'api-key',
166
+ action: 'DELETE_FUNCTION',
167
+ module: 'FUNCTIONS',
168
+ details: {
169
+ slug,
170
+ },
171
+ ip_address: req.ip,
172
+ });
173
+
174
+ res.json({
175
+ success: true,
176
+ message: `Function ${slug} deleted successfully`,
177
+ });
178
+ } catch {
179
+ res.status(500).json({ error: 'Failed to delete function' });
180
+ }
181
+ });
182
+
183
+ export default router;