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,310 @@
1
+ import { DatabaseManager } from '@/core/database/manager.js';
2
+ import {
3
+ EdgeFunctionMetadataSchema,
4
+ FunctionUploadRequest,
5
+ FunctionUpdateRequest,
6
+ } from '@insforge/shared-schemas';
7
+ import logger from '@/utils/logger.js';
8
+ import { DatabaseError } from 'pg';
9
+ import fetch from 'node-fetch';
10
+ import { AppError } from '@/api/middleware/error.js';
11
+ import { ERROR_CODES } from '@/types/error-constants.js';
12
+
13
+ export interface FunctionWithRuntime {
14
+ functions: Record<string, unknown>[];
15
+ runtime: {
16
+ status: 'running' | 'unavailable';
17
+ };
18
+ }
19
+
20
+ export class FunctionsService {
21
+ private static instance: FunctionsService;
22
+ private db;
23
+
24
+ private constructor() {
25
+ this.db = DatabaseManager.getInstance();
26
+ }
27
+
28
+ static getInstance(): FunctionsService {
29
+ if (!FunctionsService.instance) {
30
+ FunctionsService.instance = new FunctionsService();
31
+ }
32
+ return FunctionsService.instance;
33
+ }
34
+
35
+ /**
36
+ * List all functions with runtime health check
37
+ */
38
+ async listFunctions(): Promise<FunctionWithRuntime> {
39
+ try {
40
+ const functions = await this.db
41
+ .prepare(
42
+ `SELECT
43
+ id, slug, name, description, status,
44
+ created_at, updated_at, deployed_at
45
+ FROM _functions
46
+ ORDER BY created_at DESC`
47
+ )
48
+ .all();
49
+
50
+ // Check if Deno runtime is healthy
51
+ let runtimeHealthy = false;
52
+ try {
53
+ const denoUrl = process.env.DENO_RUNTIME_URL || 'http://localhost:7133';
54
+ const healthResponse = await fetch(`${denoUrl}/health`, {
55
+ method: 'GET',
56
+ signal: AbortSignal.timeout(2000), // 2 second timeout
57
+ });
58
+ runtimeHealthy = healthResponse.ok;
59
+ } catch (error) {
60
+ logger.debug('Deno runtime health check failed', {
61
+ error: error instanceof Error ? error.message : String(error),
62
+ });
63
+ }
64
+
65
+ return {
66
+ functions,
67
+ runtime: {
68
+ status: runtimeHealthy ? 'running' : 'unavailable',
69
+ },
70
+ };
71
+ } catch (error) {
72
+ logger.error('Failed to list functions', {
73
+ error: error instanceof Error ? error.message : String(error),
74
+ operation: 'listFunctions',
75
+ });
76
+ throw error;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Get a specific function by slug
82
+ */
83
+ async getFunction(slug: string): Promise<Record<string, unknown> | undefined> {
84
+ try {
85
+ const func = await this.db
86
+ .prepare(
87
+ `SELECT
88
+ id, slug, name, description, code, status,
89
+ created_at, updated_at, deployed_at
90
+ FROM _functions
91
+ WHERE slug = ?`
92
+ )
93
+ .get(slug);
94
+
95
+ return func;
96
+ } catch (error) {
97
+ logger.error('Failed to get function', {
98
+ error: error instanceof Error ? error.message : String(error),
99
+ operation: 'getFunction',
100
+ slug,
101
+ });
102
+ throw error;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Create a new function
108
+ */
109
+ async createFunction(data: FunctionUploadRequest): Promise<Record<string, unknown>> {
110
+ try {
111
+ const { name, code, description, status } = data;
112
+ const slug = data.slug || name.toLowerCase().replace(/\s+/g, '-');
113
+
114
+ // Basic security validation
115
+ this.validateCode(code);
116
+
117
+ // Generate UUID
118
+ const id = crypto.randomUUID();
119
+
120
+ // Insert function
121
+ await this.db
122
+ .prepare(
123
+ `INSERT INTO _functions (id, slug, name, description, code, status)
124
+ VALUES (?, ?, ?, ?, ?, ?)`
125
+ )
126
+ .run(id, slug, name, description || null, code, status);
127
+
128
+ // If status is active, update deployed_at
129
+ if (status === 'active') {
130
+ await this.db
131
+ .prepare(`UPDATE _functions SET deployed_at = CURRENT_TIMESTAMP WHERE id = ?`)
132
+ .run(id);
133
+ }
134
+
135
+ // Fetch the created function
136
+ const created = await this.db
137
+ .prepare(
138
+ `SELECT id, slug, name, description, status, created_at
139
+ FROM _functions WHERE id = ?`
140
+ )
141
+ .get(id);
142
+
143
+ return created;
144
+ } catch (error) {
145
+ // Re-throw AppErrors as-is
146
+ if (error instanceof AppError) {
147
+ throw error;
148
+ }
149
+
150
+ logger.error('Failed to create function', {
151
+ error: error instanceof Error ? error.message : String(error),
152
+ operation: 'createFunction',
153
+ });
154
+
155
+ // Handle unique constraint error
156
+ if (error instanceof DatabaseError && error.code === '23505') {
157
+ throw new AppError(
158
+ 'Function with this slug already exists',
159
+ 409,
160
+ ERROR_CODES.ALREADY_EXISTS
161
+ );
162
+ }
163
+
164
+ throw error;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Update an existing function
170
+ */
171
+ async updateFunction(
172
+ slug: string,
173
+ updates: FunctionUpdateRequest
174
+ ): Promise<Record<string, unknown> | null> {
175
+ try {
176
+ // Check if function exists
177
+ const existing = await this.db.prepare('SELECT id FROM _functions WHERE slug = ?').get(slug);
178
+ if (!existing) {
179
+ return null;
180
+ }
181
+
182
+ // Validate code if provided
183
+ if (updates.code !== undefined) {
184
+ this.validateCode(updates.code);
185
+ }
186
+
187
+ // Update fields
188
+ if (updates.name !== undefined) {
189
+ await this.db
190
+ .prepare('UPDATE _functions SET name = ? WHERE slug = ?')
191
+ .run(updates.name, slug);
192
+ }
193
+
194
+ if (updates.description !== undefined) {
195
+ await this.db
196
+ .prepare('UPDATE _functions SET description = ? WHERE slug = ?')
197
+ .run(updates.description, slug);
198
+ }
199
+
200
+ if (updates.code !== undefined) {
201
+ await this.db
202
+ .prepare('UPDATE _functions SET code = ? WHERE slug = ?')
203
+ .run(updates.code, slug);
204
+ }
205
+
206
+ if (updates.status !== undefined) {
207
+ await this.db
208
+ .prepare('UPDATE _functions SET status = ? WHERE slug = ?')
209
+ .run(updates.status, slug);
210
+
211
+ // Update deployed_at if status changes to active
212
+ if (updates.status === 'active') {
213
+ await this.db
214
+ .prepare('UPDATE _functions SET deployed_at = CURRENT_TIMESTAMP WHERE slug = ?')
215
+ .run(slug);
216
+ }
217
+ }
218
+
219
+ // Update updated_at
220
+ await this.db
221
+ .prepare('UPDATE _functions SET updated_at = CURRENT_TIMESTAMP WHERE slug = ?')
222
+ .run(slug);
223
+
224
+ // Fetch updated function
225
+ const updated = await this.db
226
+ .prepare(
227
+ `SELECT id, slug, name, description, status, updated_at
228
+ FROM _functions WHERE slug = ?`
229
+ )
230
+ .get(slug);
231
+
232
+ return updated;
233
+ } catch (error) {
234
+ logger.error('Failed to update function', {
235
+ error: error instanceof Error ? error.message : String(error),
236
+ operation: 'updateFunction',
237
+ slug,
238
+ });
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Delete a function
245
+ */
246
+ async deleteFunction(slug: string): Promise<boolean> {
247
+ try {
248
+ const result = await this.db.prepare('DELETE FROM _functions WHERE slug = ?').run(slug);
249
+
250
+ if (result.changes === 0) {
251
+ return false;
252
+ }
253
+
254
+ return true;
255
+ } catch (error) {
256
+ logger.error('Failed to delete function', {
257
+ error: error instanceof Error ? error.message : String(error),
258
+ operation: 'deleteFunction',
259
+ slug,
260
+ });
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Get functions metadata (public method for non-admin users)
267
+ */
268
+ async getMetadata(): Promise<Array<EdgeFunctionMetadataSchema>> {
269
+ try {
270
+ const functions = await this.db
271
+ .prepare(
272
+ `SELECT slug, name, description, status
273
+ FROM _functions
274
+ ORDER BY created_at DESC`
275
+ )
276
+ .all();
277
+
278
+ return functions as Array<EdgeFunctionMetadataSchema>;
279
+ } catch (error) {
280
+ logger.error('Failed to get edge functions metadata', {
281
+ error: error instanceof Error ? error.message : String(error),
282
+ });
283
+ return [];
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Validate function code for dangerous patterns
289
+ */
290
+ private validateCode(code: string): void {
291
+ const dangerousPatterns = [
292
+ /Deno\.run/i,
293
+ /Deno\.spawn/i,
294
+ /Deno\.Command/i,
295
+ /child_process/i,
296
+ /process\.exit/i,
297
+ /require\(['"]fs['"]\)/i,
298
+ ];
299
+
300
+ for (const pattern of dangerousPatterns) {
301
+ if (pattern.test(code)) {
302
+ throw new AppError(
303
+ `Code contains potentially dangerous pattern: ${pattern.toString()}`,
304
+ 400,
305
+ ERROR_CODES.INVALID_INPUT
306
+ );
307
+ }
308
+ }
309
+ }
310
+ }
@@ -0,0 +1,76 @@
1
+ import { LogSource, AnalyticsLogRecord, LogSourceStats } from '@/types/logs.js';
2
+ import logger from '@/utils/logger.js';
3
+ import { isCloudEnvironment } from '@/utils/environment.js';
4
+ import { LocalDBProvider } from './providers/localdb.provider.js';
5
+ import { CloudWatchProvider } from './providers/cloudwatch.provider.js';
6
+ import { AnalyticsProvider } from './providers/base.provider.js';
7
+
8
+ export class AnalyticsManager {
9
+ private static instance: AnalyticsManager;
10
+ private provider!: AnalyticsProvider;
11
+
12
+ private constructor() {}
13
+
14
+ static getInstance(): AnalyticsManager {
15
+ if (!AnalyticsManager.instance) {
16
+ AnalyticsManager.instance = new AnalyticsManager();
17
+ }
18
+ return AnalyticsManager.instance;
19
+ }
20
+
21
+ async initialize(): Promise<void> {
22
+ // Decide provider based on explicit override or cloud environment
23
+ const explicitProvider = (process.env.ANALYTICS_PROVIDER || '').toLowerCase();
24
+ const shouldUseCloudwatch =
25
+ explicitProvider === 'cloudwatch' ||
26
+ (!explicitProvider && isCloudEnvironment() && !!process.env.CLOUDWATCH_LOG_GROUP);
27
+
28
+ logger.info(
29
+ `Using analytics provider: ${shouldUseCloudwatch ? 'CloudWatch' : 'LocalDB/Postgres'}`
30
+ );
31
+
32
+ if (shouldUseCloudwatch) {
33
+ this.provider = new CloudWatchProvider();
34
+ } else {
35
+ this.provider = new LocalDBProvider();
36
+ }
37
+
38
+ await this.provider.initialize();
39
+ }
40
+
41
+ async getLogSources(): Promise<LogSource[]> {
42
+ return this.provider.getLogSources();
43
+ }
44
+
45
+ async getLogsBySource(
46
+ sourceName: string,
47
+ limit: number = 100,
48
+ beforeTimestamp?: string
49
+ ): Promise<{
50
+ logs: AnalyticsLogRecord[];
51
+ total: number;
52
+ tableName: string;
53
+ }> {
54
+ return this.provider.getLogsBySource(sourceName, limit, beforeTimestamp);
55
+ }
56
+
57
+ async getLogSourceStats(): Promise<LogSourceStats[]> {
58
+ return this.provider.getLogSourceStats();
59
+ }
60
+
61
+ async searchLogs(
62
+ query: string,
63
+ sourceName?: string,
64
+ limit: number = 100,
65
+ offset: number = 0
66
+ ): Promise<{
67
+ logs: (AnalyticsLogRecord & { source: string })[];
68
+ total: number;
69
+ }> {
70
+ return this.provider.searchLogs(query, sourceName, limit, offset);
71
+ }
72
+
73
+ async close(): Promise<void> {
74
+ await this.provider.close();
75
+ }
76
+ }
@@ -0,0 +1,255 @@
1
+ import { DatabaseManager } from '@/core/database/manager.js';
2
+ import logger from '@/utils/logger.js';
3
+ import { AppError } from '@/api/middleware/error.js';
4
+ import { ERROR_CODES } from '@/types/error-constants.js';
5
+ import type { AuditLogEntry, AuditLogQuery } from '@/types/logs.js';
6
+ import { AuditLogSchema, GetAuditLogStatsResponse } from '@insforge/shared-schemas';
7
+
8
+ export class AuditService {
9
+ private static instance: AuditService;
10
+ private db: ReturnType<DatabaseManager['getDb']>;
11
+
12
+ private constructor() {
13
+ const dbManager = DatabaseManager.getInstance();
14
+ this.db = dbManager.getDb();
15
+ logger.info('AuditService initialized');
16
+ }
17
+
18
+ public static getInstance(): AuditService {
19
+ if (!AuditService.instance) {
20
+ AuditService.instance = new AuditService();
21
+ }
22
+ return AuditService.instance;
23
+ }
24
+
25
+ /**
26
+ * Create a new audit log entry
27
+ */
28
+ async log(entry: AuditLogEntry): Promise<AuditLogSchema> {
29
+ try {
30
+ const result = await this.db
31
+ .prepare(
32
+ `INSERT INTO _audit_logs (actor, action, module, details, ip_address)
33
+ VALUES ($1, $2, $3, $4, $5)
34
+ RETURNING *`
35
+ )
36
+ .get(
37
+ entry.actor,
38
+ entry.action,
39
+ entry.module,
40
+ entry.details ? JSON.stringify(entry.details) : null,
41
+ entry.ip_address || null
42
+ );
43
+
44
+ logger.info('Audit log created', {
45
+ actor: entry.actor,
46
+ action: entry.action,
47
+ module: entry.module,
48
+ });
49
+
50
+ return {
51
+ id: result.id,
52
+ actor: result.actor,
53
+ action: result.action,
54
+ module: result.module,
55
+ details: result.details,
56
+ ipAddress: result.ip_address,
57
+ createdAt: result.created_at,
58
+ updatedAt: result.updated_at,
59
+ };
60
+ } catch (error) {
61
+ logger.error('Failed to create audit log', error);
62
+ throw new AppError('Failed to create audit log', 500, ERROR_CODES.INTERNAL_ERROR);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Query audit logs with filters and return both records and total count
68
+ */
69
+ async query(query: AuditLogQuery): Promise<{ records: AuditLogSchema[]; total: number }> {
70
+ try {
71
+ // Build base WHERE clause
72
+ let whereClause = 'WHERE 1=1';
73
+ const params: unknown[] = [];
74
+ let paramIndex = 1;
75
+
76
+ if (query.actor) {
77
+ whereClause += ` AND actor = $${paramIndex++}`;
78
+ params.push(query.actor);
79
+ }
80
+
81
+ if (query.action) {
82
+ whereClause += ` AND action = $${paramIndex++}`;
83
+ params.push(query.action);
84
+ }
85
+
86
+ if (query.module) {
87
+ whereClause += ` AND module = $${paramIndex++}`;
88
+ params.push(query.module);
89
+ }
90
+
91
+ if (query.start_date) {
92
+ whereClause += ` AND created_at >= $${paramIndex++}`;
93
+ params.push(query.start_date.toISOString());
94
+ }
95
+
96
+ if (query.end_date) {
97
+ whereClause += ` AND created_at <= $${paramIndex++}`;
98
+ params.push(query.end_date.toISOString());
99
+ }
100
+
101
+ // Get total count first
102
+ const countSql = `SELECT COUNT(*) as count FROM _audit_logs ${whereClause}`;
103
+ const countResult = (await this.db.prepare(countSql).get(...params)) as { count: number };
104
+ const total = countResult.count;
105
+
106
+ // Get paginated records
107
+ let dataSql = `SELECT * FROM _audit_logs ${whereClause} ORDER BY created_at DESC`;
108
+ const dataParams = [...params];
109
+
110
+ if (query.limit) {
111
+ dataSql += ` LIMIT $${paramIndex++}`;
112
+ dataParams.push(query.limit);
113
+ }
114
+
115
+ if (query.offset) {
116
+ dataSql += ` OFFSET $${paramIndex++}`;
117
+ dataParams.push(query.offset);
118
+ }
119
+
120
+ const records = await this.db.prepare(dataSql).all(...dataParams);
121
+
122
+ return {
123
+ records: records.map((record) => ({
124
+ id: record.id,
125
+ actor: record.actor,
126
+ action: record.action,
127
+ module: record.module,
128
+ details: record.details,
129
+ ipAddress: record.ip_address,
130
+ createdAt: record.created_at,
131
+ updatedAt: record.updated_at,
132
+ })),
133
+ total,
134
+ };
135
+ } catch (error) {
136
+ logger.error('Failed to query audit logs', error);
137
+ throw new AppError('Failed to query audit logs', 500, ERROR_CODES.INTERNAL_ERROR);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Get audit log by ID
143
+ */
144
+ async getById(id: string): Promise<AuditLogSchema | null> {
145
+ try {
146
+ const result = await this.db.prepare('SELECT * FROM _audit_logs WHERE id = $1').get(id);
147
+
148
+ return result
149
+ ? {
150
+ id: result.id,
151
+ actor: result.actor,
152
+ action: result.action,
153
+ module: result.module,
154
+ details: result.details,
155
+ ipAddress: result.ip_address,
156
+ createdAt: result.created_at,
157
+ updatedAt: result.updated_at,
158
+ }
159
+ : null;
160
+ } catch (error) {
161
+ logger.error('Failed to get audit log by ID', error);
162
+ throw new AppError('Failed to get audit log', 500, ERROR_CODES.INTERNAL_ERROR);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Get audit log statistics
168
+ */
169
+ async getStats(days: number = 7): Promise<GetAuditLogStatsResponse> {
170
+ try {
171
+ const startDate = new Date();
172
+ startDate.setDate(startDate.getDate() - days);
173
+
174
+ const [totalLogs] = await this.db
175
+ .prepare('SELECT COUNT(*) as count FROM _audit_logs WHERE created_at >= $1')
176
+ .get(startDate.toISOString());
177
+
178
+ const [uniqueActors] = await this.db
179
+ .prepare('SELECT COUNT(DISTINCT actor) as count FROM _audit_logs WHERE created_at >= $1')
180
+ .get(startDate.toISOString());
181
+
182
+ const [uniqueModules] = await this.db
183
+ .prepare('SELECT COUNT(DISTINCT module) as count FROM _audit_logs WHERE created_at >= $1')
184
+ .get(startDate.toISOString());
185
+
186
+ const actionsByModule = await this.db
187
+ .prepare(
188
+ `SELECT module, COUNT(*) as count
189
+ FROM _audit_logs
190
+ WHERE created_at >= $1
191
+ GROUP BY module`
192
+ )
193
+ .all(startDate.toISOString());
194
+
195
+ const recentActivity = await this.db
196
+ .prepare(
197
+ `SELECT * FROM _audit_logs
198
+ WHERE created_at >= $1
199
+ ORDER BY created_at DESC
200
+ LIMIT 10`
201
+ )
202
+ .all(startDate.toISOString());
203
+
204
+ const moduleStats: Record<string, number> = {};
205
+ actionsByModule.forEach((row: { module: string; count: string }) => {
206
+ moduleStats[row.module] = parseInt(row.count);
207
+ });
208
+
209
+ return {
210
+ totalLogs: parseInt(totalLogs?.count || 0),
211
+ uniqueActors: parseInt(uniqueActors?.count || 0),
212
+ uniqueModules: parseInt(uniqueModules?.count || 0),
213
+ actionsByModule: moduleStats,
214
+ recentActivity: recentActivity.map((record) => ({
215
+ id: record.id,
216
+ actor: record.actor,
217
+ action: record.action,
218
+ module: record.module,
219
+ details: record.details,
220
+ ipAddress: record.ip_address,
221
+ createdAt: record.created_at,
222
+ updatedAt: record.updated_at,
223
+ })),
224
+ };
225
+ } catch (error) {
226
+ logger.error('Failed to get audit log statistics', error);
227
+ throw new AppError('Failed to get audit statistics', 500, ERROR_CODES.INTERNAL_ERROR);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Clean up old audit logs
233
+ */
234
+ async cleanup(daysToKeep: number = 90): Promise<number> {
235
+ try {
236
+ const cutoffDate = new Date();
237
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
238
+
239
+ const result = await this.db
240
+ .prepare('DELETE FROM _audit_logs WHERE created_at < $1 RETURNING id')
241
+ .all(cutoffDate.toISOString());
242
+
243
+ const deletedCount = result.length;
244
+
245
+ if (deletedCount > 0) {
246
+ logger.info(`Cleaned up ${deletedCount} audit logs older than ${daysToKeep} days`);
247
+ }
248
+
249
+ return deletedCount;
250
+ } catch (error) {
251
+ logger.error('Failed to cleanup audit logs', error);
252
+ throw new AppError('Failed to cleanup audit logs', 500, ERROR_CODES.INTERNAL_ERROR);
253
+ }
254
+ }
255
+ }