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