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