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,55 @@
1
+ // User and profile related type definitions
2
+
3
+ import { AuthRecord, IdentifiesRecord } from './auth.js';
4
+
5
+ // User profile metadata
6
+ export interface UserMetadata {
7
+ status?: 'active' | 'inactive' | 'suspended';
8
+ preferences?: {
9
+ theme?: 'light' | 'dark';
10
+ language?: string;
11
+ notifications?: boolean;
12
+ };
13
+ settings?: {
14
+ [key: string]: string | number | boolean;
15
+ };
16
+ custom?: {
17
+ [key: string]: string | number | boolean | null;
18
+ };
19
+ }
20
+
21
+ // Profile database record
22
+ export interface ProfileRecord {
23
+ id: string;
24
+ auth_id: string;
25
+ name: string;
26
+ avatar_url?: string;
27
+ bio?: string;
28
+ metadata?: UserMetadata;
29
+ created_at: string;
30
+ updated_at: string;
31
+ }
32
+
33
+ // Combined user with profile and identities
34
+ export interface UserWithProfile extends AuthRecord {
35
+ profile: ProfileRecord;
36
+ identities: IdentifiesRecord[];
37
+ }
38
+
39
+ // Request to create a new user
40
+ export interface CreateUserRequest {
41
+ email: string;
42
+ password: string;
43
+ name: string;
44
+ avatar_url?: string;
45
+ bio?: string;
46
+ metadata?: UserMetadata;
47
+ }
48
+
49
+ // Request to update profile
50
+ export interface UpdateProfileRequest {
51
+ name?: string;
52
+ avatar_url?: string;
53
+ bio?: string;
54
+ metadata?: UserMetadata;
55
+ }
@@ -0,0 +1,23 @@
1
+ // Storage-related type definitions
2
+ import { StorageBucketSchema } from '@insforge/shared-schemas';
3
+
4
+ // Base storage record from database
5
+ export interface StorageRecord {
6
+ key: string;
7
+ bucket: string;
8
+ size: number;
9
+ mime_type?: string;
10
+ uploaded_at: string;
11
+ }
12
+
13
+ // Bucket record from _storage_buckets table
14
+ export type BucketRecord = Omit<StorageBucketSchema, 'created_at'>;
15
+
16
+ // Form field types for file uploads
17
+ export type FormFieldValue = string | string[] | undefined;
18
+
19
+ // Processed form data from multipart requests
20
+ export interface ProcessedFormData {
21
+ fields: Record<string, FormFieldValue>;
22
+ files: Record<string, Express.Multer.File[]>;
23
+ }
@@ -0,0 +1,39 @@
1
+ import { createRemoteJWKSet, JWTPayload, jwtVerify } from 'jose';
2
+ import { AppError } from '@/api/middleware/error.js';
3
+ import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
4
+
5
+ /**
6
+ * Helper function to verify cloud backend JWT token
7
+ * Validates JWT tokens from api.insforge.dev using JWKS
8
+ */
9
+ export async function verifyCloudToken(
10
+ token: string
11
+ ): Promise<{ projectId: string; payload: JWTPayload }> {
12
+ // Create JWKS endpoint for remote key set
13
+ const JWKS = createRemoteJWKSet(
14
+ new URL((process.env.CLOUD_API_HOST || 'https://api.insforge.dev') + '/.well-known/jwks.json')
15
+ );
16
+
17
+ // Verify the token with jose
18
+ const { payload } = await jwtVerify(token, JWKS, {
19
+ algorithms: ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'],
20
+ });
21
+
22
+ // Verify project_id matches if configured
23
+ const tokenProjectId = payload['projectId'] as string;
24
+ const expectedProjectId = process.env.PROJECT_ID;
25
+
26
+ if (expectedProjectId && tokenProjectId !== expectedProjectId) {
27
+ throw new AppError(
28
+ 'Project ID mismatch',
29
+ 403,
30
+ ERROR_CODES.AUTH_UNAUTHORIZED,
31
+ NEXT_ACTION.CHECK_TOKEN
32
+ );
33
+ }
34
+
35
+ return {
36
+ projectId: tokenProjectId || expectedProjectId || 'local',
37
+ payload,
38
+ };
39
+ }
@@ -0,0 +1 @@
1
+ export const ADMIN_ID = '00000000-0000-0000-0000-000000000001';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Environment utility functions for checking runtime environment
3
+ */
4
+
5
+ /**
6
+ * Check if the application is running in a cloud environment
7
+ * Currently checks for AWS instance profile, but can be extended for other cloud providers
8
+ */
9
+ export function isCloudEnvironment(): boolean {
10
+ return !!(
11
+ process.env.AWS_INSTANCE_PROFILE_NAME && process.env.AWS_INSTANCE_PROFILE_NAME.trim().length > 0
12
+ );
13
+ }
14
+
15
+ /**
16
+ * Check if the application can use shared OAuth keys
17
+ * This is typically enabled in cloud environments to avoid storing secrets
18
+ */
19
+ export function isOAuthSharedKeysAvailable(): boolean {
20
+ return isCloudEnvironment();
21
+ }
22
+
23
+ /**
24
+ * Check if running in development mode
25
+ */
26
+ export function isDevelopment(): boolean {
27
+ return process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
28
+ }
29
+
30
+ /**
31
+ * Check if running in production mode
32
+ */
33
+ export function isProduction(): boolean {
34
+ return process.env.NODE_ENV === 'production';
35
+ }
@@ -0,0 +1,49 @@
1
+ import { ColumnType } from '@insforge/shared-schemas';
2
+
3
+ export const convertSqlTypeToColumnType = (sqlType: string): ColumnType | string => {
4
+ switch (sqlType.toLowerCase()) {
5
+ case 'uuid':
6
+ return ColumnType.UUID;
7
+ case 'timestamptz':
8
+ case 'timestamp with time zone':
9
+ return ColumnType.DATETIME;
10
+ case 'date':
11
+ return ColumnType.DATE;
12
+ case 'integer':
13
+ case 'bigint':
14
+ case 'smallint':
15
+ case 'int':
16
+ case 'int2':
17
+ case 'int4':
18
+ case 'serial':
19
+ case 'serial2':
20
+ case 'serial4':
21
+ case 'serial8':
22
+ case 'smallserial':
23
+ case 'bigserial':
24
+ return ColumnType.INTEGER;
25
+ case 'double precision':
26
+ case 'real':
27
+ case 'numeric':
28
+ case 'float':
29
+ case 'float4':
30
+ case 'float8':
31
+ case 'decimal':
32
+ return ColumnType.FLOAT;
33
+ case 'boolean':
34
+ case 'bool':
35
+ return ColumnType.BOOLEAN;
36
+ case 'json':
37
+ case 'jsonb':
38
+ case 'array':
39
+ return ColumnType.JSON;
40
+ case 'text':
41
+ case 'varchar':
42
+ case 'char':
43
+ case 'character varying':
44
+ case 'character':
45
+ return ColumnType.STRING;
46
+ default:
47
+ return sqlType.slice(0, 5);
48
+ }
49
+ };
@@ -0,0 +1,13 @@
1
+ import winston from 'winston';
2
+
3
+ const logger = winston.createLogger({
4
+ level: process.env.LOG_LEVEL || 'info',
5
+ format: winston.format.combine(
6
+ winston.format.timestamp(),
7
+ winston.format.errors({ stack: true }),
8
+ winston.format.json()
9
+ ),
10
+ transports: [new winston.transports.Console()],
11
+ });
12
+
13
+ export default logger;
@@ -0,0 +1,62 @@
1
+ import { Response } from 'express';
2
+
3
+ // Traditional REST response - data returned directly
4
+ // Error responses use standard HTTP status codes with error body
5
+
6
+ export interface ErrorResponse {
7
+ error: string;
8
+ message: string;
9
+ statusCode: number;
10
+ nextActions?: string;
11
+ }
12
+
13
+ export interface PaginationMeta {
14
+ total: number;
15
+ limit: number;
16
+ offset: number;
17
+ page?: number;
18
+ totalPages?: number;
19
+ }
20
+
21
+ // Traditional REST success response - returns data directly
22
+ export function successResponse<T>(res: Response, data: T, statusCode: number = 200) {
23
+ return res.status(statusCode).json(data);
24
+ }
25
+
26
+ // Traditional REST error response
27
+ export function errorResponse(
28
+ res: Response,
29
+ error: string,
30
+ message: string,
31
+ statusCode: number = 500,
32
+ nextActions?: string
33
+ ) {
34
+ const response: ErrorResponse = {
35
+ error,
36
+ message,
37
+ statusCode,
38
+ nextActions,
39
+ };
40
+
41
+ return res.status(statusCode).json(response);
42
+ }
43
+
44
+ // Pagination response helper - returns data with PostgREST-style pagination headers
45
+ export function paginatedResponse<T>(res: Response, data: T[], total: number, offset: number) {
46
+ // Calculate the range for Content-Range header
47
+ const start = offset;
48
+ const end = Math.min(offset + data.length - 1, total - 1);
49
+
50
+ // Set PostgREST-style pagination headers
51
+ // Format: "Content-Range: start-end/total"
52
+ // Example: "Content-Range: 0-9/200" for first 10 items out of 200
53
+ res.setHeader('Content-Range', `${start}-${end}/${total}`);
54
+
55
+ // Also set Prefer header to indicate preference was applied
56
+ res.setHeader('Preference-Applied', 'count=exact');
57
+
58
+ // Use 206 Partial Content when not returning all results, 200 when returning everything
59
+ const statusCode = data.length < total ? 206 : 200;
60
+
61
+ return res.status(statusCode).json(data);
62
+ }
@@ -0,0 +1,205 @@
1
+ import { DatabaseManager } from '@/core/database/manager.js';
2
+ import { AIConfigService } from '@/core/ai/config.js';
3
+ import { isCloudEnvironment } from '@/utils/environment.js';
4
+ import logger from '@/utils/logger.js';
5
+ import { SecretsService } from '@/core/secrets/secrets';
6
+ import { OAuthConfigService } from '@/core/auth/oauth.js';
7
+
8
+ /**
9
+ * Validates admin credentials are configured
10
+ * Admin is authenticated via environment variables, not stored in DB
11
+ */
12
+ function ensureFirstAdmin(adminEmail: string, adminPassword: string): void {
13
+ if (adminEmail && adminPassword) {
14
+ logger.info(`✅ Admin configured: ${adminEmail}`);
15
+ } else {
16
+ logger.warn('⚠️ Admin credentials not configured - check ADMIN_EMAIL and ADMIN_PASSWORD');
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Seeds default AI configurations for cloud environments
22
+ */
23
+ async function seedDefaultAIConfigs(): Promise<void> {
24
+ // Only seed default AI configs in cloud environment
25
+ if (!isCloudEnvironment()) {
26
+ return;
27
+ }
28
+
29
+ const aiConfigService = new AIConfigService();
30
+
31
+ // Check if AI configs already exist
32
+ const existingConfigs = await aiConfigService.findAll();
33
+
34
+ if (existingConfigs.length > 0) {
35
+ return;
36
+ }
37
+
38
+ await aiConfigService.create(
39
+ ['text', 'image'],
40
+ ['text'],
41
+ 'openrouter',
42
+ 'openai/gpt-4o',
43
+ 'You are a helpful assistant.'
44
+ );
45
+
46
+ await aiConfigService.create(
47
+ ['text', 'image'],
48
+ ['text', 'image'],
49
+ 'openrouter',
50
+ 'google/gemini-2.5-flash-image-preview'
51
+ );
52
+
53
+ logger.info('✅ Default AI models configured (cloud environment)');
54
+ }
55
+
56
+ /**
57
+ * Seeds default OAuth configurations for Google and GitHub
58
+ */
59
+ async function seedDefaultOAuthConfigs(): Promise<void> {
60
+ const oauthService = OAuthConfigService.getInstance();
61
+
62
+ try {
63
+ // Check if OAuth configs already exist
64
+ const existingConfigs = await oauthService.getAllConfigs();
65
+ const existingProviders = existingConfigs.map((config) => config.provider.toLowerCase());
66
+
67
+ // Seed Google OAuth config if not exists
68
+ if (!existingProviders.includes('google')) {
69
+ await oauthService.createConfig({
70
+ provider: 'google',
71
+ useSharedKey: true,
72
+ });
73
+ logger.info('✅ Default Google OAuth config created');
74
+ }
75
+
76
+ // Seed GitHub OAuth config if not exists
77
+ if (!existingProviders.includes('github')) {
78
+ await oauthService.createConfig({
79
+ provider: 'github',
80
+ useSharedKey: true,
81
+ });
82
+ logger.info('✅ Default GitHub OAuth config created');
83
+ }
84
+ } catch (error) {
85
+ logger.warn('Failed to seed OAuth configs', {
86
+ error: error instanceof Error ? error.message : String(error),
87
+ });
88
+ // Don't throw error as OAuth configs are optional
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Seeds OAuth configurations from local environment variables
94
+ */
95
+ async function seedLocalOAuthConfigs(): Promise<void> {
96
+ const oauthService = OAuthConfigService.getInstance();
97
+
98
+ try {
99
+ // Check if OAuth configs already exist
100
+ const existingConfigs = await oauthService.getAllConfigs();
101
+ const existingProviders = existingConfigs.map((config) => config.provider.toLowerCase());
102
+
103
+ // Seed Google OAuth config from environment variables if credentials exist
104
+ const googleClientId = process.env.GOOGLE_CLIENT_ID;
105
+ const googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;
106
+
107
+ if (googleClientId && googleClientSecret && !existingProviders.includes('google')) {
108
+ await oauthService.createConfig({
109
+ provider: 'google',
110
+ clientId: googleClientId,
111
+ clientSecret: googleClientSecret,
112
+ scopes: ['openid', 'email', 'profile'],
113
+ useSharedKey: false,
114
+ });
115
+ logger.info('✅ Google OAuth config loaded from environment variables');
116
+ }
117
+
118
+ // Seed GitHub OAuth config from environment variables if credentials exist
119
+ const githubClientId = process.env.GITHUB_CLIENT_ID;
120
+ const githubClientSecret = process.env.GITHUB_CLIENT_SECRET;
121
+
122
+ if (githubClientId && githubClientSecret && !existingProviders.includes('github')) {
123
+ await oauthService.createConfig({
124
+ provider: 'github',
125
+ clientId: githubClientId,
126
+ clientSecret: githubClientSecret,
127
+ scopes: ['user:email'],
128
+ useSharedKey: false,
129
+ });
130
+ logger.info('✅ GitHub OAuth config loaded from environment variables');
131
+ }
132
+ } catch (error) {
133
+ logger.warn('Failed to seed local OAuth configs', {
134
+ error: error instanceof Error ? error.message : String(error),
135
+ });
136
+ }
137
+ }
138
+
139
+ // Create api key, admin user, and default AI configs
140
+ export async function seedBackend(): Promise<void> {
141
+ const secretService = new SecretsService();
142
+ const dbManager = DatabaseManager.getInstance();
143
+
144
+ const adminEmail = process.env.ADMIN_EMAIL || 'admin@example.com';
145
+ const adminPassword = process.env.ADMIN_PASSWORD || 'change-this-password';
146
+
147
+ try {
148
+ logger.info(`\n🚀 Insforge Backend Starting...`);
149
+
150
+ // Validate admin credentials are configured
151
+ ensureFirstAdmin(adminEmail, adminPassword);
152
+
153
+ // Initialize API key (from env or generate)
154
+ const apiKey = await secretService.initializeApiKey();
155
+
156
+ // Get database stats
157
+ const tables = await dbManager.getUserTables();
158
+
159
+ logger.info(`✅ Database connected to PostgreSQL`, {
160
+ host: process.env.POSTGRES_HOST || 'localhost',
161
+ port: process.env.POSTGRES_PORT || '5432',
162
+ database: process.env.POSTGRES_DB || 'insforge',
163
+ });
164
+ // Database connection info is already logged above
165
+
166
+ if (tables.length > 0) {
167
+ logger.info(`✅ Found ${tables.length} user tables`);
168
+ }
169
+
170
+ // seed AI configs for cloud environment
171
+ await seedDefaultAIConfigs();
172
+
173
+ // add default OAuth configs in Cloud hosting
174
+ if (isCloudEnvironment()) {
175
+ await seedDefaultOAuthConfigs();
176
+ } else {
177
+ await seedLocalOAuthConfigs();
178
+ }
179
+
180
+ // Initialize reserved secrets for edge functions
181
+ // Add INSFORGE_INTERNAL_URL for Deno-to-backend container communication
182
+ const insforgInternalUrl = 'http://insforge:7130';
183
+ const existingSecret = await secretService.getSecretByKey('INSFORGE_INTERNAL_URL');
184
+
185
+ if (existingSecret === null) {
186
+ await secretService.createSecret({
187
+ key: 'INSFORGE_INTERNAL_URL',
188
+ isReserved: true,
189
+ value: insforgInternalUrl,
190
+ });
191
+ logger.info('✅ System secrets initialized');
192
+ }
193
+
194
+ logger.info(`API key generated: ${apiKey}`);
195
+ logger.info(`Setup complete:
196
+ - Save this API key for your apps!
197
+ - Dashboard: http://localhost:7131
198
+ - API: http://localhost:7130/api
199
+ `);
200
+ } catch (error) {
201
+ logger.error('Error during setup', {
202
+ error: error instanceof Error ? error.message : String(error),
203
+ });
204
+ }
205
+ }
@@ -0,0 +1,63 @@
1
+ import splitSqlQuery from '@databases/split-sql-query';
2
+ import sql from '@databases/sql';
3
+ import logger from './logger.js';
4
+
5
+ /**
6
+ * Parse a SQL string into individual statements, properly handling:
7
+ * - String literals with embedded semicolons
8
+ * - Escaped quotes
9
+ * - Comments (both -- and block comment style)
10
+ * - Complex nested statements
11
+ *
12
+ * @param sqlText The raw SQL text to parse
13
+ * @returns Array of SQL statement strings
14
+ * @throws Error if the SQL cannot be parsed
15
+ */
16
+ export function parseSQLStatements(sqlText: string): string[] {
17
+ if (!sqlText || typeof sqlText !== 'string') {
18
+ throw new Error('SQL text must be a non-empty string');
19
+ }
20
+
21
+ try {
22
+ // Create an SQLQuery object from the raw SQL string
23
+ const sqlQuery = sql`${sql.__dangerous__rawValue(sqlText)}`;
24
+
25
+ // splitSqlQuery correctly handles:
26
+ // - String literals with embedded semicolons
27
+ // - Escaped quotes
28
+ // - Comments (both -- and /* */ style)
29
+ // - Complex nested statements
30
+ const splitResults = splitSqlQuery(sqlQuery);
31
+
32
+ // Convert SQLQuery objects back to strings and filter
33
+ const statements = splitResults
34
+ .map((query) => {
35
+ // Extract the raw SQL text from the SQLQuery object
36
+ // Use a simple formatter that just returns the SQL text
37
+ const formatted = query.format({
38
+ escapeIdentifier: (str: string) => `"${str}"`,
39
+ formatValue: (_value: unknown, index: number) => ({
40
+ placeholder: `$${index + 1}`,
41
+ value: _value,
42
+ }),
43
+ });
44
+ return formatted.text.trim();
45
+ })
46
+ .filter((s) => {
47
+ // Remove statements that are only comments or empty
48
+ const withoutComments = s
49
+ .replace(/--.*$/gm, '') // Remove line comments
50
+ .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
51
+ .trim();
52
+ return withoutComments.length > 0;
53
+ });
54
+
55
+ logger.debug(`Parsed ${statements.length} SQL statements from input`);
56
+ return statements;
57
+ } catch (parseError) {
58
+ logger.error('Failed to parse SQL:', parseError);
59
+ throw new Error(
60
+ `Invalid SQL format: ${parseError instanceof Error ? parseError.message : String(parseError)}`
61
+ );
62
+ }
63
+ }
@@ -0,0 +1,9 @@
1
+ import crypto from 'crypto';
2
+
3
+ /**
4
+ * Generate a UUID v4
5
+ * @returns A UUID v4 string
6
+ */
7
+ export function generateUUID(): string {
8
+ return crypto.randomUUID();
9
+ }
@@ -0,0 +1,129 @@
1
+ import { AppError } from '@/api/middleware/error.js';
2
+ import { ERROR_CODES } from '@/types/error-constants.js';
3
+
4
+ export function validateEmail(email: string) {
5
+ return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
6
+ }
7
+
8
+ /**
9
+ * Validates PostgreSQL identifier names (tables, columns, etc.)
10
+ * Prevents SQL injection and ensures valid PostgreSQL identifiers
11
+ *
12
+ * Regex breakdown: ^[^"...]+ means entire string must NOT contain:
13
+ * - " (double quotes) - could break SQL queries
14
+ * - \x00-\x1F (ASCII 0-31) - control characters like null, tab, newline
15
+ * - \x7F (ASCII 127) - DEL character
16
+ */
17
+ // eslint-disable-next-line no-control-regex
18
+ const IDENTIFIER_REGEX = /^[^"\x00-\x1F\x7F]+$/;
19
+
20
+ /**
21
+ * Validates a PostgreSQL identifier (table name, column name, etc.)
22
+ * @param identifier - The identifier to validate
23
+ * @param type - Type of identifier for error messages (e.g., 'table', 'column')
24
+ * @returns true if valid
25
+ * @throws AppError if invalid
26
+ */
27
+ export function validateIdentifier(identifier: string, type: string = 'identifier'): boolean {
28
+ if (!identifier || identifier.trim().length === 0) {
29
+ throw new AppError(
30
+ `Invalid ${type} name: cannot be empty`,
31
+ 400,
32
+ ERROR_CODES.DATABASE_VALIDATION_ERROR,
33
+ `Please provide a valid ${type} name`
34
+ );
35
+ }
36
+
37
+ if (!IDENTIFIER_REGEX.test(identifier)) {
38
+ throw new AppError(
39
+ `Invalid ${type} name: cannot contain quotes or control characters`,
40
+ 400,
41
+ ERROR_CODES.DATABASE_VALIDATION_ERROR,
42
+ `The ${type} name cannot contain double quotes or control characters (tabs, newlines, etc.)`
43
+ );
44
+ }
45
+
46
+ return true;
47
+ }
48
+
49
+ /**
50
+ * Validates a PostgreSQL identifier and returns boolean without throwing
51
+ * @param identifier - The identifier to validate
52
+ * @returns true if valid, false if invalid
53
+ */
54
+ export function isValidIdentifier(identifier: string): boolean {
55
+ return Boolean(identifier && identifier.trim().length > 0 && IDENTIFIER_REGEX.test(identifier));
56
+ }
57
+
58
+ /**
59
+ * Validates table name with additional checks
60
+ * @param tableName - The table name to validate
61
+ * @param operation - The operation being performed (optional)
62
+ * @returns true if valid
63
+ * @throws AppError if invalid
64
+ */
65
+ export function validateTableName(tableName: string): boolean {
66
+ validateIdentifier(tableName, 'table');
67
+
68
+ // Prevent access to all other system tables (starting with _)
69
+ if (tableName.startsWith('_')) {
70
+ throw new AppError(
71
+ 'Access to system tables is not allowed',
72
+ 403,
73
+ ERROR_CODES.FORBIDDEN,
74
+ 'System tables (starting with _) cannot be accessed directly'
75
+ );
76
+ }
77
+
78
+ return true;
79
+ }
80
+
81
+ /**
82
+ * Gets a safe error message for identifier validation
83
+ * @param identifier - The identifier that failed validation
84
+ * @param type - Type of identifier
85
+ * @returns Safe error message
86
+ */
87
+ export function getIdentifierErrorMessage(identifier: string, type: string = 'identifier'): string {
88
+ if (!identifier || identifier.trim().length === 0) {
89
+ return `Invalid ${type} name: cannot be empty`;
90
+ }
91
+ if (!IDENTIFIER_REGEX.test(identifier)) {
92
+ return `Invalid ${type} name: cannot contain quotes or control characters`;
93
+ }
94
+ return `Invalid ${type} name`;
95
+ }
96
+
97
+ /**
98
+ * Escapes special characters for SQL LIKE patterns.
99
+ * Prevents injection attacks by escaping %, _ and \ characters which have special meaning in SQL LIKE clauses.
100
+ *
101
+ * How it works:
102
+ * - Matches any of: % (wildcard), _ (single char), or \ (escape char)
103
+ * - Replaces with: \% \_ or \\ respectively
104
+ * - This allows literal matching of these characters in LIKE patterns
105
+ *
106
+ * @param text - Text to escape for use in SQL LIKE pattern
107
+ * @returns Escaped text safe for SQL LIKE usage
108
+ * @example escapeSqlLikePattern("test_file%") → "test\_file\%"
109
+ */
110
+ export function escapeSqlLikePattern(text: string): string {
111
+ return text.replace(/([%_\\])/g, '\\$1');
112
+ }
113
+
114
+ /**
115
+ * Escapes special regex metacharacters for literal matching in regular expressions.
116
+ * Prevents regex injection by escaping all characters that have special meaning in regex.
117
+ *
118
+ * How it works:
119
+ * - Matches any regex metacharacter: . * + ? ^ $ { } ( ) | [ ] \
120
+ * - Replaces with escaped version (prefixed with \)
121
+ * - This allows creating regex patterns that match these characters literally
122
+ *
123
+ * @param text - Text to escape for use in regex patterns
124
+ * @returns Escaped text safe for regex literal matching
125
+ * @example escapeRegexPattern("test.file(1)") → "test\\.file\\(1\\)"
126
+ */
127
+ export function escapeRegexPattern(text: string): string {
128
+ return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
129
+ }