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.
- package/.dockerignore +58 -0
- package/.env.example +49 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +83 -0
- package/.github/ISSUE_TEMPLATE/config.yml +11 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +79 -0
- package/.github/copilot-instructions.md +147 -0
- package/.github/workflows/build-image.yml +65 -0
- package/.github/workflows/ci-premerge-check.yml +24 -0
- package/.github/workflows/deploy-aws.yml +130 -0
- package/.github/workflows/lint-and-format.yml +33 -0
- package/.prettierignore +65 -0
- package/.prettierrc +9 -0
- package/CHANGELOG.md +3 -0
- package/CONTRIBUTING.md +126 -0
- package/Dockerfile +27 -0
- package/GITHUB_OAUTH_SETUP.md +49 -0
- package/GOOGLE_OAUTH_SETUP.md +148 -0
- package/LICENSE +201 -0
- package/README.md +134 -0
- package/assets/Dark.svg +23 -0
- package/assets/archDiagram.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/mcpInstallv2.png +0 -0
- package/assets/sampleResponse.png +0 -0
- package/assets/signin.png +0 -0
- package/assets/userflow.png +0 -0
- package/backend/migrations/000_create-base-tables.sql +142 -0
- package/backend/migrations/001_create-helper-functions.sql +41 -0
- package/backend/migrations/002_rename-auth-tables.sql +30 -0
- package/backend/migrations/003_create-users-table.sql +56 -0
- package/backend/migrations/004_add-reload-postgrest-func.sql +24 -0
- package/backend/migrations/005_enable-project-admin-modify-users.sql +30 -0
- package/backend/migrations/006_modify-ai-usage-table.sql +25 -0
- package/backend/migrations/007_drop-metadata-table.sql +2 -0
- package/backend/migrations/008_add-system-tables.sql +77 -0
- package/backend/migrations/009_add-function-secrets.sql +24 -0
- package/backend/migrations/010_modify-ai-config-modalities.sql +93 -0
- package/backend/migrations/011_refactor-secrets-table.sql +15 -0
- package/backend/migrations/012_add-storage-uploaded-by.sql +8 -0
- package/backend/package.json +75 -0
- package/backend/src/api/middleware/auth.ts +240 -0
- package/backend/src/api/middleware/error.ts +231 -0
- package/backend/src/api/middleware/upload.ts +59 -0
- package/backend/src/api/routes/agent.ts +29 -0
- package/backend/src/api/routes/ai.ts +472 -0
- package/backend/src/api/routes/auth.oauth.ts +482 -0
- package/backend/src/api/routes/auth.ts +386 -0
- package/backend/src/api/routes/database.advance.ts +275 -0
- package/backend/src/api/routes/database.records.ts +246 -0
- package/backend/src/api/routes/database.tables.ts +161 -0
- package/backend/src/api/routes/docs.ts +66 -0
- package/backend/src/api/routes/functions.ts +183 -0
- package/backend/src/api/routes/logs.ts +150 -0
- package/backend/src/api/routes/metadata.ts +160 -0
- package/backend/src/api/routes/openapi.ts +82 -0
- package/backend/src/api/routes/secrets.ts +199 -0
- package/backend/src/api/routes/storage.ts +547 -0
- package/backend/src/api/routes/usage.ts +96 -0
- package/backend/src/core/ai/chat.ts +207 -0
- package/backend/src/core/ai/client.ts +242 -0
- package/backend/src/core/ai/config.ts +187 -0
- package/backend/src/core/ai/image.ts +156 -0
- package/backend/src/core/ai/model.ts +117 -0
- package/backend/src/core/ai/usage.ts +290 -0
- package/backend/src/core/auth/auth.ts +781 -0
- package/backend/src/core/auth/oauth.ts +398 -0
- package/backend/src/core/database/advance.ts +1074 -0
- package/backend/src/core/database/manager.ts +178 -0
- package/backend/src/core/database/table.ts +772 -0
- package/backend/src/core/documentation/agent.ts +689 -0
- package/backend/src/core/documentation/openapi.ts +856 -0
- package/backend/src/core/functions/functions.ts +310 -0
- package/backend/src/core/logs/analytics.ts +76 -0
- package/backend/src/core/logs/audit.ts +255 -0
- package/backend/src/core/logs/providers/base.provider.ts +83 -0
- package/backend/src/core/logs/providers/cloudwatch.provider.ts +510 -0
- package/backend/src/core/logs/providers/localdb.provider.ts +246 -0
- package/backend/src/core/secrets/encryption.ts +58 -0
- package/backend/src/core/secrets/secrets.ts +410 -0
- package/backend/src/core/socket/socket.ts +388 -0
- package/backend/src/core/socket/types.ts +79 -0
- package/backend/src/core/storage/storage.ts +923 -0
- package/backend/src/server.ts +288 -0
- package/backend/src/types/ai.ts +46 -0
- package/backend/src/types/auth.ts +90 -0
- package/backend/src/types/database.ts +136 -0
- package/backend/src/types/error-constants.ts +86 -0
- package/backend/src/types/logs.ts +47 -0
- package/backend/src/types/profile.ts +55 -0
- package/backend/src/types/storage.ts +23 -0
- package/backend/src/utils/cloud-token.ts +39 -0
- package/backend/src/utils/constants.ts +1 -0
- package/backend/src/utils/environment.ts +35 -0
- package/backend/src/utils/helpers.ts +49 -0
- package/backend/src/utils/logger.ts +13 -0
- package/backend/src/utils/response.ts +62 -0
- package/backend/src/utils/seed.ts +205 -0
- package/backend/src/utils/sql-parser.ts +63 -0
- package/backend/src/utils/uuid.ts +9 -0
- package/backend/src/utils/validations.ts +129 -0
- package/backend/tests/README.md +134 -0
- package/backend/tests/cleanup-all-test-data.sh +231 -0
- package/backend/tests/cloud/test-s3-multitenant.sh +132 -0
- package/backend/tests/local/comprehensive-curl-tests.sh +156 -0
- package/backend/tests/local/test-auth-router.sh +144 -0
- package/backend/tests/local/test-database-router.sh +222 -0
- package/backend/tests/local/test-e2e.sh +241 -0
- package/backend/tests/local/test-fk-errors.sh +97 -0
- package/backend/tests/local/test-id-field.sh +201 -0
- package/backend/tests/local/test-public-bucket.sh +265 -0
- package/backend/tests/local/test-secrets.sh +248 -0
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -0
- package/backend/tests/local/test-traditional-rest.sh +209 -0
- package/backend/tests/manual/README.md +51 -0
- package/backend/tests/manual/create-large-table-simple.sql +11 -0
- package/backend/tests/manual/seed-large-table.sql +101 -0
- package/backend/tests/manual/setup-large-table-extras.sql +34 -0
- package/backend/tests/manual/test-better-auth.sh +303 -0
- package/backend/tests/manual/test-bulk-upsert.sh +410 -0
- package/backend/tests/manual/test-database-advance.sh +297 -0
- package/backend/tests/manual/test-postgrest-stability.sh +192 -0
- package/backend/tests/manual/test-rawsql-export-import.sh +412 -0
- package/backend/tests/manual/test-universal-storage.sh +264 -0
- package/backend/tests/manual/test-users.sql +18 -0
- package/backend/tests/run-all-tests.sh +140 -0
- package/backend/tests/setup.ts +22 -0
- package/backend/tests/test-config.sh +303 -0
- package/backend/tsconfig.json +23 -0
- package/backend/tsup.config.ts +18 -0
- package/backend/vitest.config.ts +22 -0
- package/docker-compose.prod.yml +145 -0
- package/docker-compose.yml +167 -0
- package/docker-init/db/db-init.sql +125 -0
- package/docker-init/db/jwt.sql +5 -0
- package/docker-init/db/logs.sql +9 -0
- package/docker-init/db/postgresql.conf +17 -0
- package/docs/deprecated/insforge-auth-api.md +215 -0
- package/docs/deprecated/insforge-auth-sdk.md +100 -0
- package/docs/deprecated/insforge-db-api.md +359 -0
- package/docs/deprecated/insforge-db-sdk.md +140 -0
- package/docs/deprecated/insforge-debug-sdk.md +157 -0
- package/docs/deprecated/insforge-debug.md +65 -0
- package/docs/deprecated/insforge-instructions.md +124 -0
- package/docs/deprecated/insforge-project.md +118 -0
- package/docs/deprecated/insforge-storage-api.md +279 -0
- package/docs/deprecated/insforge-storage-sdk.md +159 -0
- package/docs/insforge-instructions-sdk.md +407 -0
- package/eslint.config.js +317 -0
- package/examples/oauth/frontend-oauth-example.html +251 -0
- package/examples/response-examples.md +444 -0
- package/frontend/README.md +112 -0
- package/frontend/components.json +17 -0
- package/frontend/index.html +13 -0
- package/frontend/package.json +63 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/App.tsx +106 -0
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -0
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -0
- package/frontend/src/assets/icons/checked.svg +3 -0
- package/frontend/src/assets/icons/error.svg +3 -0
- package/frontend/src/assets/icons/pencil.svg +4 -0
- package/frontend/src/assets/icons/refresh.svg +4 -0
- package/frontend/src/assets/icons/step_active.svg +3 -0
- package/frontend/src/assets/icons/step_inactive.svg +11 -0
- package/frontend/src/assets/icons/warning.svg +3 -0
- package/frontend/src/assets/logos/amazon.svg +1 -0
- package/frontend/src/assets/logos/claude_code.svg +3 -0
- package/frontend/src/assets/logos/cline.svg +6 -0
- package/frontend/src/assets/logos/cursor.svg +20 -0
- package/frontend/src/assets/logos/discord.svg +9 -0
- package/frontend/src/assets/logos/gemini.svg +19 -0
- package/frontend/src/assets/logos/github.svg +5 -0
- package/frontend/src/assets/logos/google.svg +13 -0
- package/frontend/src/assets/logos/grok.svg +10 -0
- package/frontend/src/assets/logos/insforge_dark.svg +15 -0
- package/frontend/src/assets/logos/insforge_light.svg +15 -0
- package/frontend/src/assets/logos/openai.svg +10 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -0
- package/frontend/src/assets/logos/trae.svg +3 -0
- package/frontend/src/assets/logos/windsurf.svg +10 -0
- package/frontend/src/components/ButtonWithLoading.tsx +27 -0
- package/frontend/src/components/Checkbox.tsx +61 -0
- package/frontend/src/components/CodeBlock.tsx +32 -0
- package/frontend/src/components/ConfirmDialog.tsx +96 -0
- package/frontend/src/components/CopyButton.tsx +69 -0
- package/frontend/src/components/DeleteActionButton.tsx +42 -0
- package/frontend/src/components/EmptyState.tsx +41 -0
- package/frontend/src/components/ErrorState.tsx +35 -0
- package/frontend/src/components/FeatureSidebar.tsx +126 -0
- package/frontend/src/components/FeatureSidebarItem.tsx +101 -0
- package/frontend/src/components/JsonHighlight.tsx +61 -0
- package/frontend/src/components/LoadingState.tsx +16 -0
- package/frontend/src/components/PaginationControls.tsx +54 -0
- package/frontend/src/components/PromptDialog.tsx +68 -0
- package/frontend/src/components/SearchInput.tsx +90 -0
- package/frontend/src/components/SelectionClearButton.tsx +26 -0
- package/frontend/src/components/Stepper.tsx +139 -0
- package/frontend/src/components/ThemeToggle.tsx +58 -0
- package/frontend/src/components/TypeBadge.tsx +20 -0
- package/frontend/src/components/datagrid/DataGrid.tsx +264 -0
- package/frontend/src/components/datagrid/DefaultCellRenderer.tsx +114 -0
- package/frontend/src/components/datagrid/IdCell.tsx +44 -0
- package/frontend/src/components/datagrid/SortableHeader.tsx +74 -0
- package/frontend/src/components/datagrid/cell-editors/BooleanCellEditor.tsx +54 -0
- package/frontend/src/components/datagrid/cell-editors/DateCellEditor.tsx +483 -0
- package/frontend/src/components/datagrid/cell-editors/JsonCellEditor.tsx +362 -0
- package/frontend/src/components/datagrid/cell-editors/TextCellEditor.tsx +38 -0
- package/frontend/src/components/datagrid/cell-editors/index.ts +14 -0
- package/frontend/src/components/datagrid/cell-editors/types.ts +43 -0
- package/frontend/src/components/datagrid/datagridTypes.tsx +72 -0
- package/frontend/src/components/datagrid/index.tsx +20 -0
- package/frontend/src/components/index.ts +39 -0
- package/frontend/src/components/layout/AppHeader.tsx +146 -0
- package/frontend/src/components/layout/AppSidebar.tsx +190 -0
- package/frontend/src/components/layout/CloudLayout.tsx +95 -0
- package/frontend/src/components/layout/Layout.tsx +43 -0
- package/frontend/src/components/radix/Alert.tsx +45 -0
- package/frontend/src/components/radix/AlertDialog.tsx +115 -0
- package/frontend/src/components/radix/Avatar.tsx +45 -0
- package/frontend/src/components/radix/Badge.tsx +33 -0
- package/frontend/src/components/radix/Button.tsx +50 -0
- package/frontend/src/components/radix/Card.tsx +58 -0
- package/frontend/src/components/radix/Dialog.tsx +98 -0
- package/frontend/src/components/radix/DropdownMenu.tsx +185 -0
- package/frontend/src/components/radix/Form.tsx +167 -0
- package/frontend/src/components/radix/Input.tsx +22 -0
- package/frontend/src/components/radix/Label.tsx +19 -0
- package/frontend/src/components/radix/Popover.tsx +29 -0
- package/frontend/src/components/radix/ScrollArea.tsx +44 -0
- package/frontend/src/components/radix/Select.tsx +151 -0
- package/frontend/src/components/radix/Separator.tsx +26 -0
- package/frontend/src/components/radix/Sheet.tsx +119 -0
- package/frontend/src/components/radix/Skeleton.tsx +7 -0
- package/frontend/src/components/radix/Switch.tsx +29 -0
- package/frontend/src/components/radix/Tabs.tsx +50 -0
- package/frontend/src/components/radix/Textarea.tsx +21 -0
- package/frontend/src/components/radix/Tooltip.tsx +28 -0
- package/frontend/src/features/ai/components/AIConfigCard.tsx +154 -0
- package/frontend/src/features/ai/components/AIConfigDialog.tsx +76 -0
- package/frontend/src/features/ai/components/AIConfigForm.tsx +222 -0
- package/frontend/src/features/ai/components/AIEmptyState.tsx +18 -0
- package/frontend/src/features/ai/components/fields/ModalityField.tsx +87 -0
- package/frontend/src/features/ai/components/fields/ModelSelectionField.tsx +134 -0
- package/frontend/src/features/ai/components/fields/SystemPromptField.tsx +33 -0
- package/frontend/src/features/ai/helpers.ts +155 -0
- package/frontend/src/features/ai/hooks/useAIConfigs.ts +221 -0
- package/frontend/src/features/ai/hooks/useAIUsage.ts +77 -0
- package/frontend/src/features/ai/page/AIPage.tsx +178 -0
- package/frontend/src/features/ai/services/ai.service.ts +148 -0
- package/frontend/src/features/auth/components/AddOAuthDialog.tsx +106 -0
- package/frontend/src/features/auth/components/AuthMethodTab.tsx +238 -0
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +303 -0
- package/frontend/src/features/auth/components/OAuthEmptyState.tsx +15 -0
- package/frontend/src/features/auth/components/UserFormDialog.tsx +248 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +183 -0
- package/frontend/src/features/auth/components/UsersTab.tsx +114 -0
- package/frontend/src/features/auth/hooks/useOAuthConfig.ts +129 -0
- package/frontend/src/features/auth/hooks/useUsers.ts +57 -0
- package/frontend/src/features/auth/index.ts +9 -0
- package/frontend/src/features/auth/page/AuthenticationPage.tsx +169 -0
- package/frontend/src/features/auth/services/auth.service.ts +112 -0
- package/frontend/src/features/auth/services/oauth.service.ts +49 -0
- package/frontend/src/features/dashboard/page/DashboardPage.tsx +194 -0
- package/frontend/src/features/database/components/ColumnTypeSelect.tsx +64 -0
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +282 -0
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +187 -0
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +378 -0
- package/frontend/src/features/database/components/LinkRecordModal.tsx +288 -0
- package/frontend/src/features/database/components/RecordFormDialog.tsx +164 -0
- package/frontend/src/features/database/components/RecordFormField.tsx +568 -0
- package/frontend/src/features/database/components/TableEmptyState.tsx +21 -0
- package/frontend/src/features/database/components/TableForm.tsx +656 -0
- package/frontend/src/features/database/components/TableFormColumn.tsx +137 -0
- package/frontend/src/features/database/components/TableListSkeleton.tsx +9 -0
- package/frontend/src/features/database/components/TableSidebar.tsx +47 -0
- package/frontend/src/features/database/constants.ts +26 -0
- package/frontend/src/features/database/helpers.ts +125 -0
- package/frontend/src/features/database/hooks/UseLinkModal.tsx +78 -0
- package/frontend/src/features/database/index.ts +12 -0
- package/frontend/src/features/database/page/DatabasePage.tsx +626 -0
- package/frontend/src/features/database/schema.ts +25 -0
- package/frontend/src/features/database/services/database.service.ts +216 -0
- package/frontend/src/features/functions/components/FunctionEmptyState.tsx +15 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +71 -0
- package/frontend/src/features/functions/components/FunctionViewer.tsx +46 -0
- package/frontend/src/features/functions/components/FunctionsContent.tsx +88 -0
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -0
- package/frontend/src/features/functions/components/SecretEmptyState.tsx +23 -0
- package/frontend/src/features/functions/components/SecretRow.tsx +68 -0
- package/frontend/src/features/functions/components/SecretsContent.tsx +120 -0
- package/frontend/src/features/functions/hooks/useFunctions.ts +106 -0
- package/frontend/src/features/functions/page/FunctionsPage.tsx +28 -0
- package/frontend/src/features/functions/services/functions.service.ts +48 -0
- package/frontend/src/features/login/components/AuthErrorBoundary.tsx +87 -0
- package/frontend/src/features/login/components/PrivateRoute.tsx +24 -0
- package/frontend/src/features/login/page/CloudLoginPage.tsx +93 -0
- package/frontend/src/features/login/page/LoginPage.tsx +174 -0
- package/frontend/src/features/logs/components/AnalyticsLogsTable.tsx +313 -0
- package/frontend/src/features/logs/components/LogsTable.tsx +199 -0
- package/frontend/src/features/logs/hooks/useAuditLogs.ts +39 -0
- package/frontend/src/features/logs/index.ts +5 -0
- package/frontend/src/features/logs/page/AnalyticsLogsPage.tsx +530 -0
- package/frontend/src/features/logs/page/AuditsPage.tsx +192 -0
- package/frontend/src/features/logs/services/log.service.ts +171 -0
- package/frontend/src/features/metadata/hooks/useMetadata.ts +53 -0
- package/frontend/src/features/metadata/index.ts +0 -0
- package/frontend/src/features/metadata/page/MetadataPage.tsx +136 -0
- package/frontend/src/features/metadata/services/metadata.service.ts +17 -0
- package/frontend/src/features/onboard/components/CompletionCard.tsx +41 -0
- package/frontend/src/features/onboard/components/OnboardButton.tsx +84 -0
- package/frontend/src/features/onboard/components/StepContent.tsx +91 -0
- package/frontend/src/features/onboard/components/TestConnectionStep.tsx +53 -0
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +35 -0
- package/frontend/src/features/onboard/components/mcp/McpInstallation.tsx +144 -0
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -0
- package/frontend/src/features/onboard/components/mcp/mcp-helper.tsx +98 -0
- package/frontend/src/features/onboard/index.ts +3 -0
- package/frontend/src/features/onboard/page/OnBoardPage.tsx +104 -0
- package/frontend/src/features/onboard/types.ts +8 -0
- package/frontend/src/features/secrets/hooks/useSecrets.ts +139 -0
- package/frontend/src/features/secrets/services/secrets.service.ts +57 -0
- package/frontend/src/features/storage/components/BucketEmptyState.tsx +19 -0
- package/frontend/src/features/storage/components/BucketFormDialog.tsx +194 -0
- package/frontend/src/features/storage/components/BucketListSkeleton.tsx +17 -0
- package/frontend/src/features/storage/components/FilePreviewDialog.tsx +287 -0
- package/frontend/src/features/storage/components/StorageDataGrid.tsx +239 -0
- package/frontend/src/features/storage/components/StorageManager.tsx +236 -0
- package/frontend/src/features/storage/components/StorageSidebar.tsx +44 -0
- package/frontend/src/features/storage/components/UploadToast.tsx +46 -0
- package/frontend/src/features/storage/index.ts +3 -0
- package/frontend/src/features/storage/page/StoragePage.tsx +553 -0
- package/frontend/src/features/storage/services/storage.service.ts +144 -0
- package/frontend/src/features/visualizer/components/AuthNode.tsx +107 -0
- package/frontend/src/features/visualizer/components/BucketNode.tsx +34 -0
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +359 -0
- package/frontend/src/features/visualizer/components/TableNode.tsx +152 -0
- package/frontend/src/features/visualizer/components/VisualizerSkeleton.tsx +24 -0
- package/frontend/src/features/visualizer/components/index.ts +5 -0
- package/frontend/src/features/visualizer/page/VisualizerPage.tsx +127 -0
- package/frontend/src/index.css +248 -0
- package/frontend/src/lib/api/client.ts +163 -0
- package/frontend/src/lib/contexts/AuthContext.tsx +157 -0
- package/frontend/src/lib/contexts/OnboardStepContext.tsx +68 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +303 -0
- package/frontend/src/lib/contexts/ThemeContext.tsx +125 -0
- package/frontend/src/lib/hooks/useAuth.ts +4 -0
- package/frontend/src/lib/hooks/useConfirm.ts +55 -0
- package/frontend/src/lib/hooks/useInterval.ts +27 -0
- package/frontend/src/lib/hooks/useMediaQuery.ts +59 -0
- package/frontend/src/lib/hooks/useOnboardingCompletion.ts +29 -0
- package/frontend/src/lib/hooks/usePagination.ts +27 -0
- package/frontend/src/lib/hooks/useTimeout.ts +27 -0
- package/frontend/src/lib/hooks/useToast.tsx +229 -0
- package/frontend/src/lib/utils/constants.ts +38 -0
- package/frontend/src/lib/utils/utils.ts +165 -0
- package/frontend/src/lib/utils/validation-schemas.ts +126 -0
- package/frontend/src/main.tsx +16 -0
- package/frontend/src/rdg.css +194 -0
- package/frontend/src/vite-env.d.ts +12 -0
- package/frontend/tailwind.config.js +97 -0
- package/frontend/tsconfig.json +26 -0
- package/frontend/tsconfig.node.json +10 -0
- package/frontend/vite.config.ts +37 -0
- package/frontend/vitest.config.ts +36 -0
- package/functions/deno.json +25 -0
- package/functions/server.ts +290 -0
- package/functions/worker-template.js +126 -0
- package/openapi/ai.yaml +689 -0
- package/openapi/auth.yaml +563 -0
- package/openapi/functions.yaml +476 -0
- package/openapi/health.yaml +30 -0
- package/openapi/logs.yaml +224 -0
- package/openapi/metadata.yaml +178 -0
- package/openapi/records.yaml +382 -0
- package/openapi/secrets.yaml +371 -0
- package/openapi/storage.yaml +876 -0
- package/openapi/tables.yaml +464 -0
- package/package.json +88 -0
- package/shared-schemas/package.json +31 -0
- package/shared-schemas/src/ai-api.schema.ts +167 -0
- package/shared-schemas/src/ai.schema.ts +54 -0
- package/shared-schemas/src/auth-api.schema.ts +193 -0
- package/shared-schemas/src/auth.schema.ts +94 -0
- package/shared-schemas/src/database-api.schema.ts +259 -0
- package/shared-schemas/src/database.schema.ts +69 -0
- package/shared-schemas/src/functions-api.schema.ts +25 -0
- package/shared-schemas/src/functions.schema.ts +16 -0
- package/shared-schemas/src/index.ts +13 -0
- package/shared-schemas/src/logs-api.schema.ts +49 -0
- package/shared-schemas/src/logs.schema.ts +14 -0
- package/shared-schemas/src/metadata.schema.ts +56 -0
- package/shared-schemas/src/storage-api.schema.ts +65 -0
- package/shared-schemas/src/storage.schema.ts +19 -0
- package/shared-schemas/tsconfig.json +21 -0
- 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
|
+
}
|