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,93 @@
1
+ -- Migration: 010 - Modify AI configurations table to support input/output modalities
2
+ -- This migration modifies the _ai_configs table to:
3
+ -- 1. Add new columns: input_modality and output_modality (TEXT arrays)
4
+ -- 2. Migrate existing modality data to input_modality
5
+ -- 3. Set default output_modality based on existing modality
6
+ -- 4. Drop the old modality column
7
+
8
+ DO $$
9
+ BEGIN
10
+ -- Add new columns for input and output modalities
11
+ ALTER TABLE _ai_configs
12
+ ADD COLUMN IF NOT EXISTS input_modality TEXT[] DEFAULT '{text}';
13
+
14
+ ALTER TABLE _ai_configs
15
+ ADD COLUMN IF NOT EXISTS output_modality TEXT[] DEFAULT '{text}';
16
+
17
+ -- Check if modality column exists and migrate data if it does
18
+ IF EXISTS (
19
+ SELECT 1
20
+ FROM information_schema.columns
21
+ WHERE table_schema = 'public'
22
+ AND table_name = '_ai_configs'
23
+ AND column_name = 'modality'
24
+ ) THEN
25
+ -- Migrate existing modality data to input_modality
26
+ -- For most cases, we'll set input_modality to the existing modality
27
+ -- and output_modality to the same value, only supporting text and image
28
+ UPDATE _ai_configs
29
+ SET
30
+ input_modality = CASE
31
+ WHEN modality = 'multi' THEN '{text,image}'::TEXT[]
32
+ WHEN modality = 'image' THEN '{text,image}'::TEXT[]
33
+ ELSE ARRAY[modality]::TEXT[]
34
+ END,
35
+ output_modality = CASE
36
+ WHEN modality = 'multi' THEN '{text,image}'::TEXT[]
37
+ WHEN modality = 'text' THEN '{text}'::TEXT[]
38
+ WHEN modality = 'image' THEN '{text,image}'::TEXT[]
39
+ ELSE '{text}'::TEXT[]
40
+ END
41
+ WHERE input_modality = '{text}' OR input_modality IS NULL;
42
+ END IF;
43
+
44
+ -- Make the new columns NOT NULL after migration
45
+ ALTER TABLE _ai_configs
46
+ ALTER COLUMN input_modality SET NOT NULL;
47
+
48
+ ALTER TABLE _ai_configs
49
+ ALTER COLUMN output_modality SET NOT NULL;
50
+
51
+ -- Drop the old modality column
52
+ ALTER TABLE _ai_configs
53
+ DROP COLUMN IF EXISTS modality;
54
+
55
+ -- Create indexes for the new TEXT array columns for better query performance
56
+ CREATE INDEX IF NOT EXISTS idx_ai_configs_input_modality ON _ai_configs USING GIN (input_modality);
57
+ CREATE INDEX IF NOT EXISTS idx_ai_configs_output_modality ON _ai_configs USING GIN (output_modality);
58
+
59
+ -- Drop existing constraints if they exist, then add them
60
+ ALTER TABLE _ai_configs
61
+ DROP CONSTRAINT IF EXISTS check_input_modality_not_empty;
62
+
63
+ ALTER TABLE _ai_configs
64
+ ADD CONSTRAINT check_input_modality_not_empty
65
+ CHECK (array_length(input_modality, 1) > 0);
66
+
67
+ ALTER TABLE _ai_configs
68
+ DROP CONSTRAINT IF EXISTS check_output_modality_not_empty;
69
+
70
+ ALTER TABLE _ai_configs
71
+ ADD CONSTRAINT check_output_modality_not_empty
72
+ CHECK (array_length(output_modality, 1) > 0);
73
+
74
+ -- Drop existing constraints if they exist, then add them
75
+ ALTER TABLE _ai_configs
76
+ DROP CONSTRAINT IF EXISTS check_input_modality_valid;
77
+
78
+ ALTER TABLE _ai_configs
79
+ ADD CONSTRAINT check_input_modality_valid
80
+ CHECK (
81
+ input_modality <@ '{text,image}'::TEXT[]
82
+ );
83
+
84
+ ALTER TABLE _ai_configs
85
+ DROP CONSTRAINT IF EXISTS check_output_modality_valid;
86
+
87
+ ALTER TABLE _ai_configs
88
+ ADD CONSTRAINT check_output_modality_valid
89
+ CHECK (
90
+ output_modality <@ '{text,image}'::TEXT[]
91
+ );
92
+
93
+ END $$;
@@ -0,0 +1,15 @@
1
+ -- Migration: 011 - Drop function secrets table and update main secrets table
2
+ -- This migration is part of the refactoring to unify all secrets management
3
+
4
+ -- 1. Drop the _function_secrets table (replaced by main _secrets table)
5
+ DROP TRIGGER IF EXISTS update__function_secrets_updated_at ON _function_secrets;
6
+ DROP INDEX IF EXISTS idx_function_secrets_key;
7
+ DROP TABLE IF EXISTS _function_secrets;
8
+
9
+ -- 2. Add is_reserved column to _secrets table
10
+ ALTER TABLE _secrets
11
+ ADD COLUMN IF NOT EXISTS is_reserved BOOLEAN DEFAULT FALSE;
12
+
13
+ -- 3. Rename name column to key
14
+ ALTER TABLE _secrets
15
+ RENAME COLUMN name TO key;
@@ -0,0 +1,8 @@
1
+ -- Migration: 012 - Add uploaded_by column to _storage table
2
+ -- This migration adds a foreign key relationship to track which account uploaded each file
3
+
4
+ ALTER TABLE _storage
5
+ ADD COLUMN uploaded_by UUID REFERENCES _accounts(id) ON DELETE SET NULL;
6
+
7
+ -- Create an index for better query performance when filtering by uploader
8
+ CREATE INDEX IF NOT EXISTS idx_storage_uploaded_by ON _storage(uploaded_by);
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "insforge-backend",
3
+ "version": "1.0.0",
4
+ "author": "Insforge",
5
+ "description": "Open source backend-as-a-service with PostgreSQL and MCP integration",
6
+ "type": "module",
7
+ "main": "../dist/server.js",
8
+ "scripts": {
9
+ "start": "node ../dist/server.js",
10
+ "dev": "tsx watch src/server.ts",
11
+ "build": "tsup",
12
+ "clean": "rimraf dist",
13
+ "prebuild": "npm run clean",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "test:coverage": "vitest run --coverage",
17
+ "test:ui": "vitest --ui",
18
+ "test:e2e": "./tests/run-all-tests.sh",
19
+ "migrate:up": "node-pg-migrate up --migrations-table _migrations",
20
+ "migrate:down": "node-pg-migrate down --migrations-table _migrations",
21
+ "migrate:create": "node-pg-migrate create --migrations-table _migrations",
22
+ "migrate:redo": "node-pg-migrate redo --migrations-table _migrations",
23
+ "migrate:up:local": "dotenv -e ../.env -- node-pg-migrate up --migrations-table _migrations",
24
+ "migrate:down:local": "dotenv -e ../.env -- node-pg-migrate down --migrations-table _migrations"
25
+ },
26
+ "keywords": [
27
+ "backend-as-a-service",
28
+ "postgresql",
29
+ "jwt"
30
+ ],
31
+ "dependencies": {
32
+ "@asteasolutions/zod-to-openapi": "^7.3.4",
33
+ "@aws-sdk/client-cloudwatch-logs": "^3.713.0",
34
+ "@aws-sdk/client-s3": "^3.713.0",
35
+ "@aws-sdk/s3-presigned-post": "^3.879.0",
36
+ "@aws-sdk/s3-request-presigner": "^3.879.0",
37
+ "@databases/split-sql-query": "^1.0.4",
38
+ "@databases/sql": "^3.3.0",
39
+ "@types/multer": "^1.4.13",
40
+ "@types/node-pg-migrate": "^2.3.1",
41
+ "@types/pg": "^8.15.4",
42
+ "@types/socket.io": "^3.0.2",
43
+ "axios": "^1.11.0",
44
+ "bcryptjs": "^3.0.2",
45
+ "cors": "^2.8.5",
46
+ "csv-parse": "^6.1.0",
47
+ "dotenv": "^16.4.5",
48
+ "express": "^4.19.2",
49
+ "express-rate-limit": "^7.1.5",
50
+ "google-auth-library": "^10.1.0",
51
+ "jose": "^6.0.12",
52
+ "jsonwebtoken": "^9.0.2",
53
+ "multer": "^2.0.2",
54
+ "node-fetch": "^3.3.2",
55
+ "node-pg-migrate": "^8.0.3",
56
+ "openai": "^5.19.1",
57
+ "pg": "^8.16.3",
58
+ "pg-format": "^1.0.4",
59
+ "socket.io": "^4.8.1",
60
+ "winston": "^3.17.0",
61
+ "zod": "^3.23.8"
62
+ },
63
+ "devDependencies": {
64
+ "@types/cors": "^2.8.17",
65
+ "@types/express": "^4.17.21",
66
+ "@types/jsonwebtoken": "^9.0.9",
67
+ "@types/node": "^20.11.24",
68
+ "@types/pg-format": "^1.0.5",
69
+ "dotenv-cli": "^10.0.0",
70
+ "tsup": "^8.5.0",
71
+ "tsx": "^4.7.1",
72
+ "typescript": "^5.3.3",
73
+ "vitest": "^3.2.4"
74
+ }
75
+ }
@@ -0,0 +1,240 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { AuthService } from '@/core/auth/auth.js';
3
+ import { AppError } from './error.js';
4
+ import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
5
+ import { verifyCloudToken } from '@/utils/cloud-token.js';
6
+ import { SecretsService } from '@/core/secrets/secrets.js';
7
+
8
+ export interface AuthRequest extends Request {
9
+ user?: {
10
+ id: string;
11
+ email: string;
12
+ role: string;
13
+ };
14
+ authenticated?: boolean;
15
+ apiKey?: string;
16
+ projectId?: string;
17
+ }
18
+
19
+ const authService = AuthService.getInstance();
20
+ const secretService = new SecretsService();
21
+
22
+ // Helper function to extract Bearer token
23
+ function extractBearerToken(authHeader: string | undefined): string | null {
24
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
25
+ return null;
26
+ }
27
+ return authHeader.substring(7);
28
+ }
29
+
30
+ // Helper function to extract API key from request
31
+ // Checks both Bearer token (if starts with 'ik_') and x-api-key header
32
+ export function extractApiKey(req: AuthRequest): string | null {
33
+ const bearerToken = extractBearerToken(req.headers.authorization);
34
+ if (bearerToken && bearerToken.startsWith('ik_')) {
35
+ return bearerToken;
36
+ }
37
+
38
+ // Fall back to x-api-key header for backward compatibility
39
+ if (req.headers['x-api-key']) {
40
+ return req.headers['x-api-key'] as string;
41
+ }
42
+
43
+ return null;
44
+ }
45
+
46
+ // Helper function to set user on request
47
+ function setRequestUser(req: AuthRequest, payload: { sub: string; email: string; role: string }) {
48
+ req.user = {
49
+ id: payload.sub,
50
+ email: payload.email,
51
+ role: payload.role,
52
+ };
53
+ }
54
+
55
+ /**
56
+ * Verifies user authentication (accepts both user and admin tokens)
57
+ */
58
+ export async function verifyUser(req: AuthRequest, res: Response, next: NextFunction) {
59
+ const apiKey = extractApiKey(req);
60
+ if (apiKey) {
61
+ return verifyApiKey(req, res, next);
62
+ }
63
+
64
+ // Use the main verifyToken that handles JWT authentication
65
+ return verifyToken(req, res, next);
66
+ }
67
+
68
+ /**
69
+ * Verifies admin authentication (requires admin token)
70
+ */
71
+ export async function verifyAdmin(req: AuthRequest, res: Response, next: NextFunction) {
72
+ const apiKey = extractApiKey(req);
73
+ if (apiKey) {
74
+ return verifyApiKey(req, res, next);
75
+ }
76
+
77
+ try {
78
+ const token = extractBearerToken(req.headers.authorization);
79
+ if (!token) {
80
+ throw new AppError(
81
+ 'No admin token provided',
82
+ 401,
83
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
84
+ NEXT_ACTION.CHECK_TOKEN
85
+ );
86
+ }
87
+
88
+ // For admin, we use JWT tokens
89
+ const payload = authService.verifyToken(token);
90
+
91
+ if (payload.role !== 'project_admin') {
92
+ throw new AppError(
93
+ 'Admin access required',
94
+ 403,
95
+ ERROR_CODES.AUTH_UNAUTHORIZED,
96
+ NEXT_ACTION.CHECK_ADMIN_TOKEN
97
+ );
98
+ }
99
+
100
+ setRequestUser(req, payload);
101
+ next();
102
+ } catch (error) {
103
+ if (error instanceof AppError) {
104
+ next(error);
105
+ } else {
106
+ next(
107
+ new AppError(
108
+ 'Invalid admin token',
109
+ 401,
110
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
111
+ NEXT_ACTION.CHECK_ADMIN_TOKEN
112
+ )
113
+ );
114
+ }
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Verifies API key authentication
120
+ * Accepts API key via Authorization: Bearer header or x-api-key header (backward compatibility)
121
+ */
122
+ export async function verifyApiKey(req: AuthRequest, _res: Response, next: NextFunction) {
123
+ try {
124
+ // Extract API key from request using helper
125
+ const apiKey = extractApiKey(req);
126
+
127
+ if (!apiKey) {
128
+ throw new AppError(
129
+ 'No API key provided',
130
+ 401,
131
+ ERROR_CODES.AUTH_INVALID_API_KEY,
132
+ NEXT_ACTION.CHECK_API_KEY
133
+ );
134
+ }
135
+
136
+ const isValid = await secretService.verifyApiKey(apiKey);
137
+ if (!isValid) {
138
+ throw new AppError(
139
+ 'Invalid API key',
140
+ 401,
141
+ ERROR_CODES.AUTH_INVALID_API_KEY,
142
+ NEXT_ACTION.CHECK_API_KEY
143
+ );
144
+ }
145
+ req.authenticated = true;
146
+ req.apiKey = apiKey;
147
+ next();
148
+ } catch (error) {
149
+ next(error);
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Core token verification middleware that handles JWT tokens
155
+ * Sets req.user with the authenticated user information
156
+ */
157
+ export function verifyToken(req: AuthRequest, _res: Response, next: NextFunction) {
158
+ try {
159
+ const token = extractBearerToken(req.headers.authorization);
160
+ if (!token) {
161
+ throw new AppError(
162
+ 'No token provided',
163
+ 401,
164
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
165
+ NEXT_ACTION.CHECK_TOKEN
166
+ );
167
+ }
168
+
169
+ // Verify JWT token
170
+ const payload = authService.verifyToken(token);
171
+
172
+ // Validate token has a role
173
+ if (!payload.role) {
174
+ throw new AppError(
175
+ 'Invalid token: missing role',
176
+ 401,
177
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
178
+ NEXT_ACTION.CHECK_TOKEN
179
+ );
180
+ }
181
+
182
+ // Set user info on request
183
+ setRequestUser(req, payload);
184
+
185
+ next();
186
+ } catch (error) {
187
+ if (error instanceof AppError) {
188
+ next(error);
189
+ } else {
190
+ next(
191
+ new AppError(
192
+ 'Invalid token',
193
+ 401,
194
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
195
+ NEXT_ACTION.CHECK_TOKEN
196
+ )
197
+ );
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Verifies JWT token from cloud backend (api.insforge.dev)
204
+ * Validates signature using JWKS and checks project_id claim
205
+ */
206
+ export async function verifyCloudBackend(req: AuthRequest, _res: Response, next: NextFunction) {
207
+ try {
208
+ const token = extractBearerToken(req.headers.authorization);
209
+ if (!token) {
210
+ throw new AppError(
211
+ 'No authorization token provided',
212
+ 401,
213
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
214
+ NEXT_ACTION.CHECK_TOKEN
215
+ );
216
+ }
217
+
218
+ // Use helper function to verify cloud token
219
+ const { projectId } = await verifyCloudToken(token);
220
+
221
+ // Set project_id on request for use in route handlers
222
+ req.projectId = projectId;
223
+ req.authenticated = true;
224
+
225
+ next();
226
+ } catch (error) {
227
+ if (error instanceof AppError) {
228
+ next(error);
229
+ } else {
230
+ next(
231
+ new AppError(
232
+ 'Invalid cloud backend token',
233
+ 401,
234
+ ERROR_CODES.AUTH_INVALID_CREDENTIALS,
235
+ NEXT_ACTION.CHECK_TOKEN
236
+ )
237
+ );
238
+ }
239
+ }
240
+ }
@@ -0,0 +1,231 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { DatabaseError } from 'pg';
3
+ import { errorResponse } from '@/utils/response.js';
4
+ import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
5
+ import logger from '@/utils/logger.js';
6
+
7
+ export class AppError extends Error {
8
+ constructor(
9
+ public message: string,
10
+ public statusCode: number = 500,
11
+ public code: string,
12
+ public nextActions?: string
13
+ ) {
14
+ super(message);
15
+ this.name = 'AppError';
16
+ }
17
+ }
18
+
19
+ // PostgreSQL error code handlers
20
+ const POSTGRES_ERROR_HANDLERS: Record<
21
+ string,
22
+ (err: DatabaseError) => {
23
+ code: string;
24
+ message: string;
25
+ statusCode: number;
26
+ nextActions?: string;
27
+ }
28
+ > = {
29
+ // Integrity constraint violations
30
+ '23505': (err) => {
31
+ // unique_violation
32
+ const detail = err.detail || '';
33
+ const fieldMatch = detail.match(/Key \(([\w_]+)\)=/);
34
+ const fieldName = fieldMatch ? fieldMatch[1] : 'field';
35
+ return {
36
+ code: ERROR_CODES.ALREADY_EXISTS,
37
+ message: err.message,
38
+ statusCode: 409,
39
+ nextActions: NEXT_ACTION.CHECK_UNIQUE_FIELD(fieldName),
40
+ };
41
+ },
42
+ '23503': (err) => {
43
+ // foreign_key_violation
44
+ return {
45
+ code: ERROR_CODES.DATABASE_CONSTRAINT_VIOLATION,
46
+ message: err.message,
47
+ statusCode: 400,
48
+ nextActions: NEXT_ACTION.CHECK_REFERENCE_EXISTS,
49
+ };
50
+ },
51
+ '23502': (err) => {
52
+ // not_null_violation
53
+ const column = err.column || '';
54
+ return {
55
+ code: ERROR_CODES.MISSING_FIELD,
56
+ message: err.message,
57
+ statusCode: 400,
58
+ nextActions: NEXT_ACTION.FILL_REQUIRED_FIELD(column),
59
+ };
60
+ },
61
+ '42P01': (err) => ({
62
+ // undefined_table
63
+ code: ERROR_CODES.DATABASE_VALIDATION_ERROR,
64
+ message: err.message,
65
+ statusCode: 400,
66
+ nextActions: NEXT_ACTION.CHECK_TABLE_EXISTS,
67
+ }),
68
+ '42701': (err) => {
69
+ // duplicate_column
70
+ const message = err.message || '';
71
+ const columnMatch = message.match(/column "([^"]+)"/);
72
+ const columnName = columnMatch ? columnMatch[1] : '';
73
+ return {
74
+ code: ERROR_CODES.DATABASE_VALIDATION_ERROR,
75
+ message: err.message,
76
+ statusCode: 400,
77
+ nextActions: NEXT_ACTION.REMOVE_DUPLICATE_COLUMN(columnName),
78
+ };
79
+ },
80
+ '42703': (err) => ({
81
+ // undefined_column
82
+ code: ERROR_CODES.DATABASE_VALIDATION_ERROR,
83
+ message: err.message,
84
+ statusCode: 400,
85
+ nextActions: NEXT_ACTION.CHECK_COLUMN_EXISTS,
86
+ }),
87
+ '42830': (err) => ({
88
+ // invalid_foreign_key
89
+ code: ERROR_CODES.DATABASE_VALIDATION_ERROR,
90
+ message: err.message,
91
+ statusCode: 400,
92
+ nextActions: NEXT_ACTION.CHECK_UNIQUE_CONSTRAINT,
93
+ }),
94
+ '42804': (err) => ({
95
+ // datatype_mismatch
96
+ code: ERROR_CODES.DATABASE_VALIDATION_ERROR,
97
+ message: err.message,
98
+ statusCode: 400,
99
+ nextActions: NEXT_ACTION.CHECK_DATATYPE_MATCH,
100
+ }),
101
+ };
102
+
103
+ // Handle database-specific errors
104
+ function handleDatabaseError(
105
+ err: DatabaseError
106
+ ): { code: string; message: string; statusCode: number; nextActions?: string } | null {
107
+ // Check PostgreSQL error codes
108
+ if (err.code && POSTGRES_ERROR_HANDLERS[err.code]) {
109
+ return POSTGRES_ERROR_HANDLERS[err.code](err);
110
+ }
111
+
112
+ return null;
113
+ }
114
+
115
+ // Generic error-like object that could have various properties
116
+ interface ErrorLike {
117
+ message?: string;
118
+ status?: number;
119
+ statusCode?: number;
120
+ type?: string;
121
+ expose?: boolean;
122
+ body?: unknown;
123
+ }
124
+
125
+ // Single type guard for all error-like objects
126
+ function isErrorObject(err: unknown): err is ErrorLike {
127
+ return typeof err === 'object' && err !== null;
128
+ }
129
+
130
+ // Helper to safely get numeric status
131
+ function getErrorStatus(err: unknown): number | undefined {
132
+ if (!isErrorObject(err)) {
133
+ return undefined;
134
+ }
135
+ if (typeof err.status === 'number') {
136
+ return err.status;
137
+ }
138
+ if (typeof err.statusCode === 'number') {
139
+ return err.statusCode;
140
+ }
141
+ return undefined;
142
+ }
143
+
144
+ export function errorMiddleware(err: unknown, _req: Request, res: Response, _next: NextFunction) {
145
+ // Only log non-authentication errors or unexpected errors
146
+ if (!(err instanceof AppError && err.statusCode === 401)) {
147
+ logger.error('Error occurred', {
148
+ error: err instanceof Error ? err.message : String(err),
149
+ stack: err instanceof Error ? err.stack : undefined,
150
+ });
151
+ }
152
+
153
+ // Handle known AppError instances
154
+ if (err instanceof AppError) {
155
+ const errorCode = err.code || getErrorCode(err.statusCode);
156
+ return errorResponse(res, errorCode, err.message, err.statusCode, err.nextActions);
157
+ }
158
+
159
+ // Handle SyntaxError from JSON.parse
160
+ if (err instanceof SyntaxError && err.message.includes('JSON')) {
161
+ return errorResponse(
162
+ res,
163
+ ERROR_CODES.INVALID_INPUT,
164
+ err.message,
165
+ 400,
166
+ 'Please ensure your request body contains valid JSON'
167
+ );
168
+ }
169
+
170
+ // Handle PostgreSQL database errors
171
+ if (err instanceof DatabaseError) {
172
+ const dbError = handleDatabaseError(err);
173
+ if (dbError) {
174
+ return errorResponse(
175
+ res,
176
+ dbError.code,
177
+ dbError.message,
178
+ dbError.statusCode,
179
+ dbError.nextActions
180
+ );
181
+ }
182
+ }
183
+
184
+ // For all other errors, check if it's an object we can work with
185
+ if (!isErrorObject(err)) {
186
+ return errorResponse(res, ERROR_CODES.INTERNAL_ERROR, 'Internal server error', 500);
187
+ }
188
+
189
+ // Handle JSON parsing errors from body-parser
190
+ if (err.type === 'entity.parse.failed' && err.status === 400) {
191
+ return errorResponse(
192
+ res,
193
+ ERROR_CODES.INVALID_INPUT,
194
+ err.message || 'Invalid JSON in request body',
195
+ 400,
196
+ 'Please ensure your request body contains valid JSON'
197
+ );
198
+ }
199
+
200
+ // Get the status code from either status or statusCode property
201
+ const status = getErrorStatus(err);
202
+ // Handle client errors (4xx)
203
+ if (status && status >= 400 && status < 500) {
204
+ const errorCode = getErrorCode(status);
205
+ const message = err.message || 'Client error';
206
+ const body = err.expose ? err.body : undefined;
207
+ return errorResponse(res, errorCode, message, status, body as string | undefined);
208
+ }
209
+
210
+ // Default internal error with optional message
211
+ const message = err.message || 'Internal server error';
212
+ return errorResponse(res, ERROR_CODES.INTERNAL_ERROR, message, 500);
213
+ }
214
+
215
+ // Helper to map status codes to error codes
216
+ function getErrorCode(statusCode: number): string {
217
+ switch (statusCode) {
218
+ case 400:
219
+ return ERROR_CODES.INVALID_INPUT;
220
+ case 401:
221
+ return ERROR_CODES.AUTH_UNAUTHORIZED;
222
+ case 403:
223
+ return ERROR_CODES.FORBIDDEN;
224
+ case 404:
225
+ return ERROR_CODES.NOT_FOUND;
226
+ case 409:
227
+ return ERROR_CODES.ALREADY_EXISTS;
228
+ default:
229
+ return ERROR_CODES.INTERNAL_ERROR;
230
+ }
231
+ }