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