insforge 1.2.10 → 1.4.8
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/.claude-plugin/marketplace.json +20 -20
- package/.dockerignore +60 -60
- package/.env.example +83 -77
- package/.github/ISSUE_TEMPLATE/bug_report.yml +36 -36
- package/.github/ISSUE_TEMPLATE/config.yml +11 -11
- package/.github/ISSUE_TEMPLATE/feature_request.yml +26 -26
- package/.github/PULL_REQUEST_TEMPLATE.md +7 -7
- package/.github/copilot-instructions.md +146 -146
- package/.github/workflows/build-image.yml +65 -65
- package/.github/workflows/ci-premerge-check.yml +23 -23
- package/.github/workflows/e2e.yml +63 -63
- package/.github/workflows/lint-and-format.yml +32 -32
- package/.prettierignore +64 -64
- package/CHANGELOG.md +46 -44
- package/CLAUDE_PLUGIN.md +104 -104
- package/CODE_OF_CONDUCT.md +128 -128
- package/CONTRIBUTING.md +125 -125
- package/Dockerfile +30 -30
- package/GITHUB_OAUTH_SETUP.md +49 -49
- package/GOOGLE_OAUTH_SETUP.md +148 -148
- package/LICENSE +201 -201
- package/README.md +182 -182
- package/assets/Dark.svg +23 -23
- package/auth/package.json +30 -28
- package/auth/src/lib/broadcastService.ts +4 -4
- package/auth/src/lib/insforge.ts +8 -0
- package/auth/src/main.tsx +2 -4
- package/auth/src/pages/SignInPage.tsx +5 -2
- package/auth/src/pages/SignUpPage.tsx +5 -2
- package/auth/src/pages/VerifyEmailPage.tsx +18 -0
- package/auth/tsconfig.json +33 -32
- package/auth/tsconfig.node.json +11 -11
- package/backend/package.json +82 -75
- package/backend/src/api/middlewares/rate-limiters.ts +127 -127
- package/backend/src/api/routes/ai/index.routes.ts +475 -468
- package/backend/src/api/routes/auth/index.routes.ts +720 -570
- package/backend/src/api/routes/auth/oauth.routes.ts +478 -448
- package/backend/src/api/routes/database/advance.routes.ts +37 -16
- package/backend/src/api/routes/database/index.routes.ts +80 -1
- package/backend/src/api/routes/database/records.routes.ts +48 -184
- package/backend/src/api/routes/database/rpc.routes.ts +69 -0
- package/backend/src/api/routes/database/tables.routes.ts +0 -14
- package/backend/src/api/routes/deployments/index.routes.ts +192 -0
- package/backend/src/api/routes/docs/index.routes.ts +76 -76
- package/backend/src/api/routes/email/index.routes.ts +35 -0
- package/backend/src/api/routes/functions/index.routes.ts +21 -15
- package/backend/src/api/routes/metadata/index.routes.ts +38 -0
- package/backend/src/api/routes/realtime/channels.routes.ts +81 -0
- package/backend/src/api/routes/realtime/index.routes.ts +12 -0
- package/backend/src/api/routes/realtime/messages.routes.ts +48 -0
- package/backend/src/api/routes/realtime/permissions.routes.ts +19 -0
- package/backend/src/api/routes/storage/index.routes.ts +18 -12
- package/backend/src/api/routes/usage/index.routes.ts +6 -4
- package/backend/src/api/routes/webhooks/index.routes.ts +109 -0
- package/backend/src/infra/database/database.manager.ts +14 -11
- package/backend/src/infra/database/migrations/000_create-base-tables.sql +141 -141
- package/backend/src/infra/database/migrations/001_create-helper-functions.sql +40 -40
- package/backend/src/infra/database/migrations/002_rename-auth-tables.sql +29 -29
- package/backend/src/infra/database/migrations/003_create-users-table.sql +55 -55
- package/backend/src/infra/database/migrations/004_add-reload-postgrest-func.sql +23 -23
- package/backend/src/infra/database/migrations/005_enable-project-admin-modify-users.sql +29 -29
- package/backend/src/infra/database/migrations/006_modify-ai-usage-table.sql +24 -24
- package/backend/src/infra/database/migrations/007_drop-metadata-table.sql +1 -1
- package/backend/src/infra/database/migrations/008_add-system-tables.sql +76 -76
- package/backend/src/infra/database/migrations/009_add-function-secrets.sql +23 -23
- package/backend/src/infra/database/migrations/010_modify-ai-config-modalities.sql +93 -93
- package/backend/src/infra/database/migrations/011_refactor-secrets-table.sql +15 -15
- package/backend/src/infra/database/migrations/012_add-storage-uploaded-by.sql +7 -7
- package/backend/src/infra/database/migrations/013_create-auth-schema-functions.sql +44 -44
- package/backend/src/infra/database/migrations/014_add-updated-at-trigger-user-table.sql +7 -7
- package/backend/src/infra/database/migrations/015_create-auth-config-and-email-otp-tables.sql +59 -59
- package/backend/src/infra/database/migrations/016_update-auth-config-and-email-otp.sql +24 -24
- package/backend/src/infra/database/migrations/017_create-realtime-schema.sql +233 -0
- package/backend/src/infra/database/migrations/018_schema-rework.sql +441 -0
- package/backend/src/infra/database/migrations/019_create-deployments-table.sql +36 -0
- package/backend/src/infra/database/migrations/020_add-audio-modality.sql +11 -0
- package/backend/src/infra/database/migrations/bootstrap/bootstrap-migrations.js +103 -0
- package/backend/src/infra/realtime/realtime.manager.ts +246 -0
- package/backend/src/infra/realtime/webhook-sender.ts +82 -0
- package/backend/src/infra/security/token.manager.ts +216 -125
- package/backend/src/infra/socket/socket.manager.ts +198 -64
- package/backend/src/providers/ai/openrouter.provider.ts +24 -12
- package/backend/src/providers/database/base.provider.ts +39 -0
- package/backend/src/providers/database/cloud.provider.ts +159 -0
- package/backend/src/providers/deployments/vercel.provider.ts +516 -0
- package/backend/src/providers/email/base.provider.ts +4 -7
- package/backend/src/providers/email/cloud.provider.ts +84 -0
- package/backend/src/providers/oauth/apple.provider.ts +266 -0
- package/backend/src/providers/oauth/index.ts +1 -0
- package/backend/src/server.ts +329 -284
- package/backend/src/services/ai/ai-config.service.ts +6 -6
- package/backend/src/services/ai/ai-model.service.ts +60 -60
- package/backend/src/services/ai/ai-usage.service.ts +7 -7
- package/backend/src/services/ai/chat-completion.service.ts +415 -220
- package/backend/src/services/ai/helpers.ts +64 -64
- package/backend/src/services/ai/image-generation.service.ts +3 -3
- package/backend/src/services/ai/index.ts +13 -13
- package/backend/src/services/auth/auth-config.service.ts +4 -4
- package/backend/src/services/auth/auth-otp.service.ts +6 -6
- package/backend/src/services/auth/auth.service.ts +148 -74
- package/backend/src/services/auth/index.ts +4 -4
- package/backend/src/services/auth/oauth-config.service.ts +12 -12
- package/backend/src/services/database/database-advance.service.ts +19 -55
- package/backend/src/services/database/database-table.service.ts +38 -94
- package/backend/src/services/database/database.service.ts +127 -0
- package/backend/src/services/database/postgrest-proxy.service.ts +165 -0
- package/backend/src/services/deployments/deployment.service.ts +693 -0
- package/backend/src/services/email/email.service.ts +5 -7
- package/backend/src/services/functions/function.service.ts +61 -41
- package/backend/src/services/logs/audit.service.ts +10 -10
- package/backend/src/services/realtime/index.ts +3 -0
- package/backend/src/services/realtime/realtime-auth.service.ts +104 -0
- package/backend/src/services/realtime/realtime-channel.service.ts +237 -0
- package/backend/src/services/realtime/realtime-message.service.ts +260 -0
- package/backend/src/services/secrets/secret.service.ts +101 -27
- package/backend/src/services/storage/storage.service.ts +30 -30
- package/backend/src/services/usage/usage.service.ts +6 -6
- package/backend/src/types/ai.ts +8 -0
- package/backend/src/types/auth.ts +16 -1
- package/backend/src/types/database.ts +2 -0
- package/backend/src/types/deployments.ts +33 -0
- package/backend/src/types/realtime.ts +18 -0
- package/backend/src/types/socket.ts +7 -31
- package/backend/src/types/storage.ts +1 -1
- package/backend/src/types/webhooks.ts +45 -0
- package/backend/src/utils/cookies.ts +34 -0
- package/backend/src/utils/environment.ts +0 -14
- package/backend/src/utils/s3-config-loader.ts +64 -0
- package/backend/src/utils/seed.ts +79 -43
- package/backend/src/utils/sql-parser.ts +216 -0
- package/backend/src/utils/utils.ts +114 -114
- package/backend/src/utils/validations.ts +10 -10
- package/backend/tests/README.md +133 -133
- package/backend/tests/cleanup-all-test-data.sh +230 -230
- package/backend/tests/cloud/test-s3-multitenant.sh +131 -131
- package/backend/tests/local/comprehensive-curl-tests.sh +155 -155
- package/backend/tests/local/test-ai-config.sh +129 -129
- package/backend/tests/local/test-ai-usage.sh +80 -80
- package/backend/tests/local/test-auth-router.sh +143 -143
- package/backend/tests/local/test-database-router.sh +222 -222
- package/backend/tests/local/test-e2e.sh +240 -240
- package/backend/tests/local/test-fk-errors.sh +96 -96
- package/backend/tests/local/test-functions.sh +123 -123
- package/backend/tests/local/test-id-field.sh +200 -200
- package/backend/tests/local/test-logs.sh +132 -132
- package/backend/tests/local/test-public-bucket.sh +264 -264
- package/backend/tests/local/test-rpc.sh +141 -0
- package/backend/tests/local/test-secrets.sh +249 -249
- package/backend/tests/local/test-serverless-functions.sh.disabled +325 -325
- package/backend/tests/local/test-traditional-rest.sh +208 -208
- package/backend/tests/manual/README.md +50 -50
- package/backend/tests/manual/create-large-table-simple.sql +10 -10
- package/backend/tests/manual/seed-large-table.sql +100 -100
- package/backend/tests/manual/setup-large-table-extras.sql +33 -33
- package/backend/tests/manual/test-ai-model-plugins.sh +258 -0
- package/backend/tests/manual/test-bulk-upsert.sh +409 -409
- package/backend/tests/manual/test-database-advance.sh +296 -296
- package/backend/tests/manual/test-postgrest-stability.sh +191 -191
- package/backend/tests/manual/test-rawsql-export-import.sh +411 -411
- package/backend/tests/manual/test-rawsql-modes.sh +244 -244
- package/backend/tests/manual/test-universal-storage.sh +263 -263
- package/backend/tests/manual/test-users.sql +17 -17
- package/backend/tests/run-all-tests.sh +139 -139
- package/backend/tests/setup.ts +0 -0
- package/backend/tests/test-config.sh +338 -338
- package/backend/tests/unit/analyze-query.test.ts +697 -0
- package/backend/tests/unit/database-advance.test.ts +326 -0
- package/backend/tests/unit/helpers.test.ts +2 -2
- package/backend/tsconfig.json +22 -22
- package/claude-plugin/.claude-plugin/plugin.json +24 -24
- package/claude-plugin/README.md +133 -133
- package/claude-plugin/skills/insforge-schema-patterns/SKILL.md +273 -270
- package/docker-compose.prod.yml +204 -200
- package/docker-compose.yml +232 -228
- package/docker-init/db/db-init.sql +97 -97
- package/docker-init/db/jwt.sql +5 -5
- package/docker-init/db/postgresql.conf +16 -16
- package/docker-init/logs/vector.yml +236 -236
- package/docs/README.md +44 -44
- package/docs/agent-docs/deployment.md +79 -0
- package/docs/agent-docs/real-time.md +269 -0
- package/docs/changelog.mdx +212 -67
- package/docs/core-concepts/ai/architecture.mdx +350 -372
- package/docs/core-concepts/ai/sdk.mdx +238 -213
- package/docs/core-concepts/authentication/architecture.mdx +276 -278
- package/docs/core-concepts/authentication/sdk.mdx +710 -414
- package/docs/core-concepts/authentication/ui-components/customization.mdx +733 -529
- package/docs/core-concepts/authentication/ui-components/nextjs.mdx +247 -221
- package/docs/core-concepts/authentication/ui-components/react-router.mdx +183 -184
- package/docs/core-concepts/authentication/ui-components/react.mdx +136 -129
- package/docs/core-concepts/database/architecture.mdx +292 -255
- package/docs/core-concepts/database/pgvector.mdx +138 -0
- package/docs/core-concepts/database/sdk.mdx +382 -382
- package/docs/core-concepts/deployments/architecture.mdx +152 -0
- package/docs/core-concepts/email/architecture.mdx +103 -0
- package/docs/core-concepts/email/sdk.mdx +53 -0
- package/docs/core-concepts/functions/architecture.mdx +105 -105
- package/docs/core-concepts/functions/sdk.mdx +183 -184
- package/docs/core-concepts/realtime/architecture.mdx +446 -0
- package/docs/core-concepts/realtime/sdk.mdx +409 -0
- package/docs/core-concepts/storage/architecture.mdx +243 -243
- package/docs/core-concepts/storage/sdk.mdx +253 -253
- package/docs/deployment/README.md +94 -94
- package/docs/deployment/deploy-to-aws-ec2.md +564 -564
- package/docs/deployment/deploy-to-azure-virtual-machines.md +312 -312
- package/docs/deployment/deploy-to-google-cloud-compute-engine.md +613 -613
- package/docs/deployment/deploy-to-render.md +441 -441
- package/docs/deprecated/insforge-auth-api.md +214 -214
- package/docs/deprecated/insforge-auth-sdk.md +99 -99
- package/docs/deprecated/insforge-db-api.md +358 -358
- package/docs/deprecated/insforge-db-sdk.md +139 -139
- package/docs/deprecated/insforge-debug-sdk.md +156 -156
- package/docs/deprecated/insforge-debug.md +64 -64
- package/docs/deprecated/insforge-instructions.md +123 -123
- package/docs/deprecated/insforge-project.md +117 -117
- package/docs/deprecated/insforge-storage-api.md +278 -278
- package/docs/deprecated/insforge-storage-sdk.md +158 -158
- package/docs/docs.json +240 -210
- package/docs/examples/framework-guides/nextjs.mdx +131 -131
- package/docs/examples/framework-guides/nuxt.mdx +165 -165
- package/docs/examples/framework-guides/react.mdx +165 -165
- package/docs/examples/framework-guides/svelte.mdx +153 -153
- package/docs/examples/framework-guides/vue.mdx +159 -159
- package/docs/examples/overview.mdx +67 -67
- package/docs/favicon.png +0 -0
- package/docs/favicon.svg +4 -19
- package/docs/images/changelog/dec-2025/ai-integration.png +0 -0
- package/docs/images/changelog/dec-2025/ai-models.webp +0 -0
- package/docs/images/changelog/dec-2025/alipay-payment.webp +0 -0
- package/docs/images/changelog/dec-2025/apple-login.jpg +0 -0
- package/docs/images/changelog/dec-2025/apple-oauth.mp4 +0 -0
- package/docs/images/changelog/dec-2025/mcp-installer.png +0 -0
- package/docs/images/changelog/dec-2025/moreModels.png +0 -0
- package/docs/images/changelog/dec-2025/multi-region.webp +0 -0
- package/docs/images/changelog/dec-2025/postgres-connection.webp +0 -0
- package/docs/images/changelog/dec-2025/realtime-module.jpg +0 -0
- package/docs/images/changelog/dec-2025/realtime2.png +0 -0
- package/docs/images/icons/ai.svg +4 -4
- package/docs/images/logos/nextjs.svg +4 -4
- package/docs/images/logos/nuxt.svg +4 -4
- package/docs/images/logos/react.svg +5 -5
- package/docs/images/logos/svelte.svg +4 -4
- package/docs/images/logos/vue.svg +5 -5
- package/docs/images/mcp-setup/CC-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/CC-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-1.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-2.mp4 +0 -0
- package/docs/images/mcp-setup/Cursor-MCP-3.mp4 +0 -0
- package/docs/images/mcp-setup/claude-code-connect.png +0 -0
- package/docs/images/mcp-setup/cline-1.png +0 -0
- package/docs/images/mcp-setup/cline-2.png +0 -0
- package/docs/images/mcp-setup/cline-3.png +0 -0
- package/docs/images/mcp-setup/connect-project.png +0 -0
- package/docs/images/mcp-setup/copilot-1.png +0 -0
- package/docs/images/mcp-setup/copilot-2.png +0 -0
- package/docs/images/mcp-setup/copilot-3.png +0 -0
- package/docs/images/mcp-setup/mcp-json-1.png +0 -0
- package/docs/images/mcp-setup/mcp-json-2.png +0 -0
- package/docs/images/mcp-setup/qoder-1.png +0 -0
- package/docs/images/mcp-setup/qoder-2.png +0 -0
- package/docs/images/mcp-setup/roocode-1.png +0 -0
- package/docs/images/mcp-setup/roocode-2.png +0 -0
- package/docs/images/mcp-setup/trae-1.png +0 -0
- package/docs/images/mcp-setup/trae-2.png +0 -0
- package/docs/images/mcp-setup/trae-3.png +0 -0
- package/docs/images/mcp-setup/trae-4.png +0 -0
- package/docs/images/mcp-setup/trae-5.png +0 -0
- package/docs/images/mcp-setup/windsurf-1.png +0 -0
- package/docs/images/mcp-setup/windsurf-2.png +0 -0
- package/docs/insforge-instructions-sdk.md +93 -88
- package/docs/introduction.mdx +46 -45
- package/docs/logo/dark.svg +22 -22
- package/docs/logo/light.svg +20 -20
- package/docs/mcp-setup.mdx +332 -0
- package/docs/oauth-server.mdx +563 -0
- package/docs/partnership.mdx +720 -646
- package/docs/quickstart.mdx +82 -82
- package/docs/showcase.mdx +52 -52
- package/docs/snippets/sdk-installation.mdx +21 -21
- package/docs/snippets/service-icons.mdx +27 -27
- package/docs/vscode-extension.mdx +74 -0
- package/eslint.config.js +1 -0
- package/examples/oauth/frontend-oauth-example.html +250 -250
- package/examples/response-examples.md +443 -443
- package/frontend/components.json +17 -17
- package/frontend/package.json +69 -69
- package/frontend/src/App.tsx +8 -3
- package/frontend/src/assets/icons/checkbox_checked.svg +6 -6
- package/frontend/src/assets/icons/checkbox_undetermined.svg +6 -6
- package/frontend/src/assets/icons/checked.svg +3 -3
- package/frontend/src/assets/icons/connected.svg +3 -3
- package/frontend/src/assets/icons/error.svg +3 -3
- package/frontend/src/assets/icons/loader.svg +9 -9
- package/frontend/src/assets/icons/pencil.svg +4 -4
- package/frontend/src/assets/icons/refresh.svg +4 -4
- package/frontend/src/assets/icons/step_active.svg +3 -3
- package/frontend/src/assets/icons/step_inactive.svg +11 -11
- package/frontend/src/assets/icons/warning.svg +3 -3
- package/frontend/src/assets/logos/antigravity.svg +1 -0
- package/frontend/src/assets/logos/apple.svg +3 -3
- package/frontend/src/assets/logos/claude_code.svg +3 -3
- package/frontend/src/assets/logos/cline.svg +6 -6
- package/frontend/src/assets/logos/copilot.svg +10 -0
- package/frontend/src/assets/logos/cursor.svg +20 -20
- package/frontend/src/assets/logos/deepseek.svg +139 -0
- package/frontend/src/assets/logos/discord.svg +8 -8
- package/frontend/src/assets/logos/facebook.svg +3 -3
- package/frontend/src/assets/logos/gemini.svg +19 -19
- package/frontend/src/assets/logos/github.svg +5 -5
- package/frontend/src/assets/logos/google.svg +13 -13
- package/frontend/src/assets/logos/grok.svg +10 -10
- package/frontend/src/assets/logos/insforge_dark.svg +15 -15
- package/frontend/src/assets/logos/insforge_light.svg +15 -15
- package/frontend/src/assets/logos/instagram.svg +1 -1
- package/frontend/src/assets/logos/kiro.svg +9 -0
- package/frontend/src/assets/logos/linkedin.svg +3 -3
- package/frontend/src/assets/logos/openai.svg +10 -10
- package/frontend/src/assets/logos/qoder.svg +4 -0
- package/frontend/src/assets/logos/qwen.svg +15 -0
- package/frontend/src/assets/logos/roo_code.svg +9 -9
- package/frontend/src/assets/logos/spotify.svg +16 -16
- package/frontend/src/assets/logos/tiktok.svg +5 -5
- package/frontend/src/assets/logos/trae.svg +3 -3
- package/frontend/src/assets/logos/windsurf.svg +10 -10
- package/frontend/src/assets/logos/x.svg +3 -3
- package/frontend/src/components/CodeBlock.tsx +2 -2
- package/frontend/src/components/ConnectCTA.tsx +3 -2
- package/frontend/src/components/datagrid/DataGrid.tsx +90 -62
- package/frontend/src/components/datagrid/datagridTypes.tsx +2 -1
- package/frontend/src/components/datagrid/index.ts +1 -1
- package/frontend/src/components/index.ts +0 -1
- package/frontend/src/components/layout/AppHeader.tsx +13 -37
- package/frontend/src/components/layout/AppSidebar.tsx +85 -100
- package/frontend/src/components/layout/Layout.tsx +34 -32
- package/frontend/src/components/layout/PrimaryMenu.tsx +12 -4
- package/frontend/src/components/radix/Select.tsx +151 -151
- package/frontend/src/features/ai/components/AIConfigCard.tsx +200 -200
- package/frontend/src/features/ai/components/AIEmptyState.tsx +23 -23
- package/frontend/src/features/ai/components/ModalityFilterSidebar.tsx +102 -101
- package/frontend/src/features/ai/components/ModelSelectionDialog.tsx +135 -135
- package/frontend/src/features/ai/components/ModelSelectionGrid.tsx +51 -51
- package/frontend/src/features/ai/components/SystemPromptDialog.tsx +118 -118
- package/frontend/src/features/ai/components/index.ts +6 -6
- package/frontend/src/features/ai/helpers.ts +147 -141
- package/frontend/src/features/ai/{page → pages}/AIPage.tsx +166 -166
- package/frontend/src/features/auth/components/AuthPreview.tsx +96 -96
- package/frontend/src/features/auth/components/OAuthConfigDialog.tsx +1 -0
- package/frontend/src/features/auth/components/UsersDataGrid.tsx +61 -31
- package/frontend/src/features/auth/components/index.ts +5 -5
- package/frontend/src/features/auth/helpers.tsx +8 -0
- package/frontend/src/features/auth/{page → pages}/AuthMethodsPage.tsx +275 -275
- package/frontend/src/features/auth/{page → pages}/UsersPage.tsx +0 -28
- package/frontend/src/features/dashboard/{page → pages}/DashboardPage.tsx +1 -1
- package/frontend/src/features/database/components/DatabaseDataGrid.tsx +0 -2
- package/frontend/src/features/database/components/ForeignKeyCell.tsx +38 -11
- package/frontend/src/features/database/components/ForeignKeyPopover.tsx +18 -8
- package/frontend/src/features/database/components/LinkRecordModal.tsx +61 -13
- package/frontend/src/features/database/components/RecordFormField.tsx +1 -1
- package/frontend/src/features/database/components/SQLModal.tsx +75 -0
- package/frontend/src/features/database/components/TableForm.tsx +0 -4
- package/frontend/src/features/database/components/TableSidebar.tsx +0 -3
- package/frontend/src/features/database/components/TablesEmptyState.tsx +1 -1
- package/frontend/src/features/database/components/TemplatePreview.tsx +1 -2
- package/frontend/src/features/database/constants.ts +16 -28
- package/frontend/src/features/database/hooks/useCSVImport.ts +3 -2
- package/frontend/src/features/database/hooks/useDatabase.ts +66 -0
- package/frontend/src/features/database/hooks/useRawSQL.ts +3 -2
- package/frontend/src/features/database/hooks/useTables.ts +30 -28
- package/frontend/src/features/database/index.ts +1 -0
- package/frontend/src/features/database/{page → pages}/FunctionsPage.tsx +29 -42
- package/frontend/src/features/database/{page → pages}/IndexesPage.tsx +34 -51
- package/frontend/src/features/database/{page → pages}/PoliciesPage.tsx +42 -58
- package/frontend/src/features/database/{page → pages}/SQLEditorPage.tsx +2 -2
- package/frontend/src/features/database/{page → pages}/TablesPage.tsx +0 -42
- package/frontend/src/features/database/{page → pages}/TriggersPage.tsx +34 -51
- package/frontend/src/features/database/services/advance.service.ts +1 -41
- package/frontend/src/features/database/services/database.service.ts +55 -0
- package/frontend/src/features/database/services/record.service.ts +4 -20
- package/frontend/src/features/database/services/table.service.ts +1 -10
- package/frontend/src/features/database/templates/ai-chatbot.ts +6 -6
- package/frontend/src/features/database/templates/ecommerce-platform.ts +2 -2
- package/frontend/src/features/database/templates/instagram-clone.ts +10 -10
- package/frontend/src/features/database/templates/notion-clone.ts +8 -8
- package/frontend/src/features/database/templates/reddit-clone.ts +10 -10
- package/frontend/src/features/deployments/components/DeploymentRow.tsx +93 -0
- package/frontend/src/features/deployments/components/DeploymentsEmptyState.tsx +15 -0
- package/frontend/src/features/deployments/hooks/useDeployments.ts +157 -0
- package/frontend/src/features/deployments/pages/DeploymentsPage.tsx +318 -0
- package/frontend/src/features/deployments/services/deployments.service.ts +63 -0
- package/frontend/src/features/functions/components/FunctionRow.tsx +72 -72
- package/frontend/src/features/functions/components/FunctionsSidebar.tsx +56 -56
- package/frontend/src/features/functions/components/SecretRow.tsx +3 -3
- package/frontend/src/features/functions/components/index.ts +5 -5
- package/frontend/src/features/functions/hooks/useFunctions.ts +5 -4
- package/frontend/src/features/functions/hooks/useSecrets.ts +6 -9
- package/frontend/src/features/functions/{page → pages}/FunctionsPage.tsx +21 -44
- package/frontend/src/features/functions/{page → pages}/SecretsPage.tsx +118 -116
- package/frontend/src/features/functions/services/function.service.ts +8 -25
- package/frontend/src/features/functions/services/secret.service.ts +23 -41
- package/frontend/src/features/login/{page → pages}/CloudLoginPage.tsx +125 -118
- package/frontend/src/features/logs/components/LogDetailPanel.tsx +41 -0
- package/frontend/src/features/logs/components/LogsDataGrid.tsx +32 -1
- package/frontend/src/features/logs/components/index.ts +1 -0
- package/frontend/src/features/logs/hooks/useMcpUsage.ts +13 -66
- package/frontend/src/features/logs/{page → pages}/LogsPage.tsx +36 -6
- package/frontend/src/features/onboard/components/ApiCredentialsSection.tsx +59 -0
- package/frontend/src/features/onboard/components/ConnectionStringSection.tsx +180 -0
- package/frontend/src/features/onboard/components/McpConnectionSection.tsx +159 -0
- package/frontend/src/features/onboard/components/OnboardingController.tsx +68 -0
- package/frontend/src/features/onboard/components/OnboardingModal.tsx +121 -267
- package/frontend/src/features/onboard/components/ShowPasswordButton.tsx +21 -0
- package/frontend/src/features/onboard/components/index.ts +9 -4
- package/frontend/src/features/onboard/components/mcp/CursorDeeplinkGenerator.tsx +1 -1
- package/frontend/src/features/onboard/components/mcp/QoderDeeplinkGenerator.tsx +36 -0
- package/frontend/src/features/onboard/components/mcp/helpers.tsx +123 -98
- package/frontend/src/features/onboard/components/mcp/index.ts +4 -3
- package/frontend/src/features/onboard/index.ts +17 -13
- package/frontend/src/features/realtime/components/ChannelRow.tsx +83 -0
- package/frontend/src/features/realtime/components/EditChannelModal.tsx +246 -0
- package/frontend/src/features/realtime/components/MessageRow.tsx +85 -0
- package/frontend/src/features/realtime/components/RealtimeEmptyState.tsx +30 -0
- package/frontend/src/features/realtime/hooks/useRealtime.ts +218 -0
- package/frontend/src/features/realtime/index.ts +11 -0
- package/frontend/src/features/realtime/pages/RealtimeChannelsPage.tsx +172 -0
- package/frontend/src/features/realtime/pages/RealtimeMessagesPage.tsx +211 -0
- package/frontend/src/features/realtime/pages/RealtimePermissionsPage.tsx +191 -0
- package/frontend/src/features/realtime/services/realtime.service.ts +107 -0
- package/frontend/src/features/settings/pages/SettingsPage.tsx +349 -0
- package/frontend/src/features/storage/{page → pages}/StoragePage.tsx +1 -29
- package/frontend/src/features/visualizer/components/AuthNode.tsx +4 -4
- package/frontend/src/features/visualizer/components/SchemaVisualizer.tsx +24 -11
- package/frontend/src/features/visualizer/{page → pages}/VisualizerPage.tsx +11 -36
- package/frontend/src/index.css +249 -249
- package/frontend/src/lib/contexts/ModalContext.tsx +35 -0
- package/frontend/src/lib/contexts/SocketContext.tsx +119 -75
- package/frontend/src/lib/hooks/useMetadata.ts +45 -1
- package/frontend/src/lib/hooks/useModal.tsx +2 -0
- package/frontend/src/lib/routing/AppRoutes.tsx +103 -84
- package/frontend/src/lib/services/metadata.service.ts +20 -3
- package/frontend/src/lib/utils/cloudMessaging.ts +1 -1
- package/frontend/src/lib/utils/menuItems.ts +223 -183
- package/frontend/src/lib/utils/utils.ts +196 -183
- package/frontend/tsconfig.json +25 -25
- package/frontend/tsconfig.node.json +9 -9
- package/functions/deno.json +24 -24
- package/functions/server.ts +6 -6
- package/functions/worker-template.js +1 -1
- package/i18n/README.ar.md +130 -130
- package/i18n/README.de.md +130 -130
- package/i18n/README.es.md +154 -154
- package/i18n/README.fr.md +134 -134
- package/i18n/README.hi.md +129 -129
- package/i18n/README.ja.md +174 -174
- package/i18n/README.ko.md +136 -136
- package/i18n/README.pt-BR.md +131 -131
- package/i18n/README.ru.md +129 -129
- package/i18n/README.zh-CN.md +133 -133
- package/openapi/ai.yaml +825 -715
- package/openapi/auth.yaml +1324 -1244
- package/openapi/email.yaml +158 -0
- package/openapi/functions.yaml +475 -475
- package/openapi/health.yaml +29 -29
- package/openapi/logs.yaml +221 -223
- package/openapi/metadata.yaml +175 -177
- package/openapi/realtime.yaml +699 -0
- package/openapi/records.yaml +381 -381
- package/openapi/secrets.yaml +370 -370
- package/openapi/storage.yaml +875 -875
- package/openapi/tables.yaml +462 -463
- package/package.json +97 -97
- package/shared-schemas/package.json +31 -31
- package/shared-schemas/src/ai-api.schema.ts +251 -143
- package/shared-schemas/src/ai.schema.ts +8 -4
- package/shared-schemas/src/auth-api.schema.ts +380 -339
- package/shared-schemas/src/auth.schema.ts +18 -11
- package/shared-schemas/src/cloud-events.schema.ts +26 -0
- package/shared-schemas/src/database-api.schema.ts +32 -1
- package/shared-schemas/src/database.schema.ts +39 -0
- package/shared-schemas/src/deployments-api.schema.ts +55 -0
- package/shared-schemas/src/deployments.schema.ts +30 -0
- package/shared-schemas/src/docs.schema.ts +32 -0
- package/shared-schemas/src/email-api.schema.ts +30 -0
- package/shared-schemas/src/functions-api.schema.ts +13 -4
- package/shared-schemas/src/functions.schema.ts +1 -1
- package/shared-schemas/src/index.ts +22 -14
- package/shared-schemas/src/metadata.schema.ts +39 -4
- package/shared-schemas/src/realtime-api.schema.ts +111 -0
- package/shared-schemas/src/realtime.schema.ts +143 -0
- package/shared-schemas/src/secrets-api.schema.ts +44 -0
- package/shared-schemas/src/secrets.schema.ts +15 -0
- package/shared-schemas/tsconfig.json +21 -21
- package/tsconfig.json +7 -7
- package/zeabur/README.md +26 -13
- package/zeabur/template.yml +1001 -1032
- package/.cursor/rules/cursor-rules.mdc +0 -94
- package/backend/src/types/profile.ts +0 -55
- package/frontend/src/components/ProjectInfoModal.tsx +0 -128
- package/frontend/src/features/database/hooks/useFullMetadata.ts +0 -18
- package/test-gemini.sh +0 -35
- package/test-usage-admin.sh +0 -57
- package/test-usage.sh +0 -50
- /package/frontend/src/features/auth/{page → pages}/ConfigurationPage.tsx +0 -0
- /package/frontend/src/features/database/{page → pages}/TemplatesPage.tsx +0 -0
- /package/frontend/src/features/login/{page → pages}/LoginPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/AuditsPage.tsx +0 -0
- /package/frontend/src/features/logs/{page → pages}/MCPLogsPage.tsx +0 -0
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@insforge/shared-schemas';
|
|
10
10
|
import logger from '@/utils/logger.js';
|
|
11
11
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
12
|
-
import { parseSQLStatements } from '@/utils/sql-parser.js';
|
|
12
|
+
import { parseSQLStatements, checkAuthSchemaOperations } from '@/utils/sql-parser.js';
|
|
13
13
|
import { validateTableName } from '@/utils/validations.js';
|
|
14
14
|
import format from 'pg-format';
|
|
15
15
|
import { parse } from 'csv-parse/sync';
|
|
@@ -66,28 +66,26 @@ export class DatabaseAdvanceService {
|
|
|
66
66
|
/**
|
|
67
67
|
* Sanitize query with strict or relaxed mode
|
|
68
68
|
*
|
|
69
|
-
*
|
|
69
|
+
* Blocks:
|
|
70
70
|
* - DROP DATABASE, CREATE DATABASE, ALTER DATABASE
|
|
71
71
|
* - pg_catalog and information_schema access
|
|
72
|
+
* - DELETE operations on auth schema (prevents user deletion via raw SQL)
|
|
73
|
+
* - TRUNCATE operations on auth schema (prevents mass user deletion)
|
|
74
|
+
* - DROP operations on auth schema (prevents destruction of tables, indexes, triggers, functions, views, sequences, schemas, policies, types, domains)
|
|
72
75
|
*
|
|
73
|
-
*
|
|
74
|
-
* -
|
|
75
|
-
* -
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* - SELECT and INSERT into system tables and users table
|
|
79
|
-
* RELAXED MODE blocks:
|
|
80
|
-
* - UPDATE/DELETE/DROP/CREATE/ALTER system tables
|
|
81
|
-
* - UPDATE/DELETE/DROP/RENAME users table
|
|
76
|
+
* Allows:
|
|
77
|
+
* - SELECT queries on auth schema (for reading user data)
|
|
78
|
+
* - INSERT operations on auth schema (for test users)
|
|
79
|
+
* - CREATE TRIGGER on auth tables (for automatic profile creation, etc.)
|
|
80
|
+
* - Other DDL operations on auth schema (ALTER TABLE for indexes, etc.)
|
|
82
81
|
*/
|
|
83
|
-
sanitizeQuery(query: string,
|
|
84
|
-
//
|
|
82
|
+
sanitizeQuery(query: string, _mode: 'strict' | 'relaxed' = 'strict'): string {
|
|
83
|
+
// Block database-level operations
|
|
85
84
|
const dangerousPatterns = [
|
|
86
85
|
/DROP\s+DATABASE/i,
|
|
87
86
|
/CREATE\s+DATABASE/i,
|
|
88
87
|
/ALTER\s+DATABASE/i,
|
|
89
88
|
/pg_catalog/i,
|
|
90
|
-
/information_schema/i,
|
|
91
89
|
];
|
|
92
90
|
|
|
93
91
|
for (const pattern of dangerousPatterns) {
|
|
@@ -96,47 +94,13 @@ export class DatabaseAdvanceService {
|
|
|
96
94
|
}
|
|
97
95
|
}
|
|
98
96
|
|
|
99
|
-
//
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Check for DROP or RENAME operations on 'users' table
|
|
110
|
-
const usersTablePattern =
|
|
111
|
-
/(?:^|\n|;)\s*(?:DROP\s+(?:TABLE\s+)?(?:IF\s+EXISTS\s+)?(?:\w+\.)?["']?users["']?|ALTER\s+TABLE\s+(?:IF\s+EXISTS\s+)?(?:\w+\.)?["']?users["']?\s+RENAME\s+TO)/im;
|
|
112
|
-
if (usersTablePattern.test(query)) {
|
|
113
|
-
throw new AppError('Cannot drop or rename the users table', 403, ERROR_CODES.FORBIDDEN);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (mode === 'strict') {
|
|
117
|
-
// Check for system table operations (tables starting with underscore)
|
|
118
|
-
// This pattern checks each statement in multi-statement queries, including schema-qualified names
|
|
119
|
-
const systemTablePattern =
|
|
120
|
-
/(?:^|\n|;)\s*(?:CREATE|ALTER|DROP|INSERT\s+INTO|UPDATE|DELETE\s+FROM|TRUNCATE)\s+(?:TABLE\s+)?(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:\w+\.)?["']?_\w+/im;
|
|
121
|
-
if (systemTablePattern.test(query)) {
|
|
122
|
-
throw new AppError(
|
|
123
|
-
'Cannot modify or create system tables (tables starting with underscore)',
|
|
124
|
-
403,
|
|
125
|
-
ERROR_CODES.FORBIDDEN
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
} else {
|
|
129
|
-
// Relaxed mode: Allow only SELECT and INSERT into system tables and users table
|
|
130
|
-
// Block UPDATE, DELETE, DROP, CREATE, ALTER, TRUNCATE
|
|
131
|
-
const systemTableDestructivePattern =
|
|
132
|
-
/(?:^|\n|;)\s*(?:CREATE|ALTER|DROP|TRUNCATE|UPDATE|DELETE\s+FROM)\s+(?:TABLE\s+)?(?:IF\s+(?:NOT\s+)?EXISTS\s+)?(?:\w+\.)?["']?_\w+/im;
|
|
133
|
-
if (systemTableDestructivePattern.test(query)) {
|
|
134
|
-
throw new AppError(
|
|
135
|
-
'Cannot UPDATE/DELETE/DROP/CREATE/ALTER system tables (tables starting with underscore)',
|
|
136
|
-
403,
|
|
137
|
-
ERROR_CODES.FORBIDDEN
|
|
138
|
-
);
|
|
139
|
-
}
|
|
97
|
+
// Use parser-based check for auth schema operations
|
|
98
|
+
const authError = checkAuthSchemaOperations(query);
|
|
99
|
+
if (authError) {
|
|
100
|
+
logger.warn('Blocked operation on auth schema', {
|
|
101
|
+
query: query.substring(0, 100),
|
|
102
|
+
});
|
|
103
|
+
throw new AppError(authError, 403, ERROR_CODES.FORBIDDEN);
|
|
140
104
|
}
|
|
141
105
|
|
|
142
106
|
return query;
|
|
@@ -30,8 +30,6 @@ const reservedColumns = {
|
|
|
30
30
|
updated_at: ColumnType.DATETIME,
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const userTableFrozenColumns = ['nickname', 'avatar_url'];
|
|
34
|
-
|
|
35
33
|
const SAFE_FUNCS = new Set(['now()', 'gen_random_uuid()']);
|
|
36
34
|
|
|
37
35
|
function getSafeDollarQuotedLiteral(s: string) {
|
|
@@ -105,7 +103,6 @@ export class DatabaseTableService {
|
|
|
105
103
|
FROM information_schema.tables
|
|
106
104
|
WHERE table_schema = 'public'
|
|
107
105
|
AND table_type = 'BASE TABLE'
|
|
108
|
-
AND table_name NOT LIKE '\\_%'
|
|
109
106
|
`
|
|
110
107
|
);
|
|
111
108
|
|
|
@@ -122,15 +119,6 @@ export class DatabaseTableService {
|
|
|
122
119
|
): Promise<CreateTableResponse> {
|
|
123
120
|
// Validate table name
|
|
124
121
|
validateIdentifier(table_name, 'table');
|
|
125
|
-
// Prevent creation of system tables
|
|
126
|
-
if (table_name.startsWith('_')) {
|
|
127
|
-
throw new AppError(
|
|
128
|
-
'Cannot create system tables',
|
|
129
|
-
403,
|
|
130
|
-
ERROR_CODES.FORBIDDEN,
|
|
131
|
-
'Table names starting with underscore are reserved for system tables'
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
122
|
|
|
135
123
|
// Filter out reserved fields with matching types, throw error for mismatched types
|
|
136
124
|
const validatedColumns = this.validateReservedFields(columns);
|
|
@@ -245,7 +233,7 @@ export class DatabaseTableService {
|
|
|
245
233
|
`
|
|
246
234
|
CREATE TRIGGER ${this.quoteIdentifier(table_name + '_update_timestamp')}
|
|
247
235
|
BEFORE UPDATE ON ${this.quoteIdentifier(table_name)}
|
|
248
|
-
FOR EACH ROW EXECUTE FUNCTION
|
|
236
|
+
FOR EACH ROW EXECUTE FUNCTION system.update_updated_at();
|
|
249
237
|
`
|
|
250
238
|
);
|
|
251
239
|
|
|
@@ -268,15 +256,6 @@ export class DatabaseTableService {
|
|
|
268
256
|
}
|
|
269
257
|
}
|
|
270
258
|
|
|
271
|
-
/**
|
|
272
|
-
* Get all table schemas
|
|
273
|
-
*/
|
|
274
|
-
async getAllTableSchemas(): Promise<GetTableSchemaResponse[]> {
|
|
275
|
-
const tables = await this.listTables();
|
|
276
|
-
const schemas = await Promise.all(tables.map((table) => this.getTableSchema(table)));
|
|
277
|
-
return schemas;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
259
|
/**
|
|
281
260
|
* Get table schema
|
|
282
261
|
*/
|
|
@@ -313,6 +292,7 @@ export class DatabaseTableService {
|
|
|
313
292
|
SELECT
|
|
314
293
|
column_name,
|
|
315
294
|
data_type,
|
|
295
|
+
udt_name,
|
|
316
296
|
is_nullable,
|
|
317
297
|
column_default,
|
|
318
298
|
character_maximum_length
|
|
@@ -383,17 +363,21 @@ export class DatabaseTableService {
|
|
|
383
363
|
|
|
384
364
|
return {
|
|
385
365
|
tableName: table,
|
|
386
|
-
columns: columns.map((col: ColumnInfo) =>
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
366
|
+
columns: columns.map((col: ColumnInfo) => {
|
|
367
|
+
// For USER-DEFINED types (extensions like pgvector), use udt_name
|
|
368
|
+
const effectiveType = col.data_type === 'USER-DEFINED' ? col.udt_name : col.data_type;
|
|
369
|
+
return {
|
|
370
|
+
columnName: col.column_name,
|
|
371
|
+
type: convertSqlTypeToColumnType(effectiveType),
|
|
372
|
+
isNullable: col.is_nullable === 'YES',
|
|
373
|
+
isPrimaryKey: pkSet.has(col.column_name),
|
|
374
|
+
isUnique: pkSet.has(col.column_name) || uniqueSet.has(col.column_name),
|
|
375
|
+
defaultValue: this.parseDefaultValue(col.column_default),
|
|
376
|
+
...(foreignKeyMap.has(col.column_name) && {
|
|
377
|
+
foreignKey: foreignKeyMap.get(col.column_name),
|
|
378
|
+
}),
|
|
379
|
+
};
|
|
380
|
+
}),
|
|
397
381
|
recordCount: row_count,
|
|
398
382
|
};
|
|
399
383
|
} finally {
|
|
@@ -411,16 +395,6 @@ export class DatabaseTableService {
|
|
|
411
395
|
const { addColumns, dropColumns, updateColumns, addForeignKeys, dropForeignKeys, renameTable } =
|
|
412
396
|
operations;
|
|
413
397
|
|
|
414
|
-
// Prevent modification of system tables
|
|
415
|
-
if (tableName.startsWith('_')) {
|
|
416
|
-
throw new AppError(
|
|
417
|
-
'System tables cannot be modified',
|
|
418
|
-
403,
|
|
419
|
-
ERROR_CODES.DATABASE_FORBIDDEN,
|
|
420
|
-
'System tables cannot be modified. System tables are prefixed with underscore.'
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
398
|
const client = await this.getPool().connect();
|
|
425
399
|
try {
|
|
426
400
|
// Check if table exists
|
|
@@ -501,14 +475,6 @@ export class DatabaseTableService {
|
|
|
501
475
|
`You cannot drop the system column '${col}'`
|
|
502
476
|
);
|
|
503
477
|
}
|
|
504
|
-
if (tableName === 'users' && userTableFrozenColumns.includes(col)) {
|
|
505
|
-
throw new AppError(
|
|
506
|
-
'cannot drop frozen users columns',
|
|
507
|
-
403,
|
|
508
|
-
ERROR_CODES.FORBIDDEN,
|
|
509
|
-
`You cannot drop the frozen users column '${col}'`
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
478
|
await client.query(
|
|
513
479
|
`
|
|
514
480
|
ALTER TABLE ${safeTableName}
|
|
@@ -531,14 +497,6 @@ export class DatabaseTableService {
|
|
|
531
497
|
`You cannot update the system column '${column.columnName}'`
|
|
532
498
|
);
|
|
533
499
|
}
|
|
534
|
-
if (tableName === 'users' && userTableFrozenColumns.includes(column.columnName)) {
|
|
535
|
-
throw new AppError(
|
|
536
|
-
'cannot update frozen user columns',
|
|
537
|
-
403,
|
|
538
|
-
ERROR_CODES.FORBIDDEN,
|
|
539
|
-
`You cannot update the frozen users column '${column.columnName}'`
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
500
|
|
|
543
501
|
// Handle default value changes
|
|
544
502
|
if (column.defaultValue !== undefined) {
|
|
@@ -629,19 +587,6 @@ export class DatabaseTableService {
|
|
|
629
587
|
}
|
|
630
588
|
|
|
631
589
|
if (renameTable && renameTable.newTableName) {
|
|
632
|
-
if (tableName === 'users') {
|
|
633
|
-
throw new AppError('Cannot rename users table', 403, ERROR_CODES.FORBIDDEN);
|
|
634
|
-
}
|
|
635
|
-
// Prevent renaming to system tables
|
|
636
|
-
if (renameTable.newTableName.startsWith('_')) {
|
|
637
|
-
throw new AppError(
|
|
638
|
-
'Cannot rename to system table',
|
|
639
|
-
403,
|
|
640
|
-
ERROR_CODES.FORBIDDEN,
|
|
641
|
-
'Table names starting with underscore are reserved for system tables'
|
|
642
|
-
);
|
|
643
|
-
}
|
|
644
|
-
|
|
645
590
|
const safeNewTableName = this.quoteIdentifier(renameTable.newTableName);
|
|
646
591
|
// Rename the table
|
|
647
592
|
await client.query(
|
|
@@ -678,19 +623,6 @@ export class DatabaseTableService {
|
|
|
678
623
|
* Delete a table
|
|
679
624
|
*/
|
|
680
625
|
async deleteTable(table: string): Promise<DeleteTableResponse> {
|
|
681
|
-
// Prevent deletion of system tables
|
|
682
|
-
if (table.startsWith('_')) {
|
|
683
|
-
throw new AppError(
|
|
684
|
-
'System tables cannot be deleted',
|
|
685
|
-
403,
|
|
686
|
-
ERROR_CODES.DATABASE_FORBIDDEN,
|
|
687
|
-
'System tables cannot be deleted. System tables are prefixed with underscore.'
|
|
688
|
-
);
|
|
689
|
-
}
|
|
690
|
-
if (table === 'users') {
|
|
691
|
-
throw new AppError('Cannot delete users table', 403, ERROR_CODES.DATABASE_FORBIDDEN);
|
|
692
|
-
}
|
|
693
|
-
|
|
694
626
|
const client = await this.getPool().connect();
|
|
695
627
|
try {
|
|
696
628
|
await client.query(`DROP TABLE IF EXISTS ${this.quoteIdentifier(table)} CASCADE`);
|
|
@@ -721,6 +653,15 @@ export class DatabaseTableService {
|
|
|
721
653
|
return `"${identifier.replace(/"/g, '""')}"`;
|
|
722
654
|
}
|
|
723
655
|
|
|
656
|
+
// Quote a table reference, with special handling for auth.users
|
|
657
|
+
private quoteTableReference(tableRef: string): string {
|
|
658
|
+
// Only allow auth.users as a cross-schema reference
|
|
659
|
+
if (tableRef === 'auth.users') {
|
|
660
|
+
return '"auth"."users"';
|
|
661
|
+
}
|
|
662
|
+
return this.quoteIdentifier(tableRef);
|
|
663
|
+
}
|
|
664
|
+
|
|
724
665
|
private validateReservedFields(columns: ColumnSchema[]): ColumnSchema[] {
|
|
725
666
|
return columns.filter((col: ColumnSchema) => {
|
|
726
667
|
const reservedType = reservedColumns[col.columnName as keyof typeof reservedColumns];
|
|
@@ -752,14 +693,16 @@ export class DatabaseTableService {
|
|
|
752
693
|
}
|
|
753
694
|
// Store foreign_key in a const to avoid repeated non-null assertions
|
|
754
695
|
const fk = col.foreignKey;
|
|
755
|
-
|
|
696
|
+
// Use "auth_users" in constraint name for auth.users references
|
|
697
|
+
const safeTableName = fk.referenceTable === 'auth.users' ? 'auth_users' : fk.referenceTable;
|
|
698
|
+
const constraintName = `fk_${col.columnName}_${safeTableName}_${fk.referenceColumn}`;
|
|
756
699
|
const onDelete = fk.onDelete || 'RESTRICT';
|
|
757
700
|
const onUpdate = fk.onUpdate || 'RESTRICT';
|
|
758
701
|
|
|
759
702
|
if (include_source_column) {
|
|
760
|
-
return `CONSTRAINT ${this.quoteIdentifier(constraintName)} FOREIGN KEY (${this.quoteIdentifier(col.columnName)}) REFERENCES ${this.
|
|
703
|
+
return `CONSTRAINT ${this.quoteIdentifier(constraintName)} FOREIGN KEY (${this.quoteIdentifier(col.columnName)}) REFERENCES ${this.quoteTableReference(fk.referenceTable)}(${this.quoteIdentifier(fk.referenceColumn)}) ON DELETE ${onDelete} ON UPDATE ${onUpdate}`;
|
|
761
704
|
} else {
|
|
762
|
-
return `CONSTRAINT ${this.quoteIdentifier(constraintName)} REFERENCES ${this.
|
|
705
|
+
return `CONSTRAINT ${this.quoteIdentifier(constraintName)} REFERENCES ${this.quoteTableReference(fk.referenceTable)}(${this.quoteIdentifier(fk.referenceColumn)}) ON DELETE ${onDelete} ON UPDATE ${onUpdate}`;
|
|
763
706
|
}
|
|
764
707
|
}
|
|
765
708
|
|
|
@@ -769,6 +712,7 @@ export class DatabaseTableService {
|
|
|
769
712
|
SELECT
|
|
770
713
|
tc.constraint_name,
|
|
771
714
|
kcu.column_name as from_column,
|
|
715
|
+
ccu.table_schema AS foreign_schema,
|
|
772
716
|
ccu.table_name AS foreign_table,
|
|
773
717
|
ccu.column_name AS foreign_column,
|
|
774
718
|
rc.delete_rule as on_delete,
|
|
@@ -779,7 +723,6 @@ export class DatabaseTableService {
|
|
|
779
723
|
AND tc.table_schema = kcu.table_schema
|
|
780
724
|
JOIN information_schema.constraint_column_usage AS ccu
|
|
781
725
|
ON ccu.constraint_name = tc.constraint_name
|
|
782
|
-
AND ccu.table_schema = tc.table_schema
|
|
783
726
|
JOIN information_schema.referential_constraints AS rc
|
|
784
727
|
ON rc.constraint_name = tc.constraint_name
|
|
785
728
|
AND rc.constraint_schema = tc.table_schema
|
|
@@ -794,13 +737,14 @@ export class DatabaseTableService {
|
|
|
794
737
|
// Create a map of column names to their foreign key info
|
|
795
738
|
const foreignKeyMap = new Map<string, ForeignKeyInfo>();
|
|
796
739
|
foreignKeys.forEach((fk: ForeignKeyRow) => {
|
|
797
|
-
if (
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
740
|
+
// Prefix table name with schema if not public (e.g., "auth.users")
|
|
741
|
+
const referenceTable =
|
|
742
|
+
fk.foreign_schema !== 'public'
|
|
743
|
+
? `${fk.foreign_schema}.${fk.foreign_table}`
|
|
744
|
+
: fk.foreign_table;
|
|
801
745
|
foreignKeyMap.set(fk.from_column, {
|
|
802
746
|
constraint_name: fk.constraint_name,
|
|
803
|
-
referenceTable
|
|
747
|
+
referenceTable,
|
|
804
748
|
referenceColumn: fk.foreign_column,
|
|
805
749
|
onDelete: fk.on_delete as OnDeleteActionSchema,
|
|
806
750
|
onUpdate: fk.on_update as OnUpdateActionSchema,
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
2
|
+
import type {
|
|
3
|
+
DatabaseFunctionsResponse,
|
|
4
|
+
DatabaseIndexesResponse,
|
|
5
|
+
DatabasePoliciesResponse,
|
|
6
|
+
DatabaseTriggersResponse,
|
|
7
|
+
} from '@insforge/shared-schemas';
|
|
8
|
+
|
|
9
|
+
export class DatabaseService {
|
|
10
|
+
private static instance: DatabaseService;
|
|
11
|
+
private dbManager = DatabaseManager.getInstance();
|
|
12
|
+
|
|
13
|
+
private constructor() {}
|
|
14
|
+
|
|
15
|
+
public static getInstance(): DatabaseService {
|
|
16
|
+
if (!DatabaseService.instance) {
|
|
17
|
+
DatabaseService.instance = new DatabaseService();
|
|
18
|
+
}
|
|
19
|
+
return DatabaseService.instance;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get all database functions (excluding system and extension functions)
|
|
24
|
+
*/
|
|
25
|
+
async getFunctions(): Promise<DatabaseFunctionsResponse> {
|
|
26
|
+
const pool = this.dbManager.getPool();
|
|
27
|
+
|
|
28
|
+
const result = await pool.query(`
|
|
29
|
+
SELECT
|
|
30
|
+
p.proname as "functionName",
|
|
31
|
+
pg_get_functiondef(p.oid) as "functionDef",
|
|
32
|
+
p.prokind as "kind"
|
|
33
|
+
FROM pg_proc p
|
|
34
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
35
|
+
WHERE n.nspname = 'public'
|
|
36
|
+
AND p.prokind IN ('f', 'p', 'w')
|
|
37
|
+
AND NOT EXISTS (
|
|
38
|
+
SELECT 1 FROM pg_depend d
|
|
39
|
+
JOIN pg_extension e ON d.refobjid = e.oid
|
|
40
|
+
WHERE d.objid = p.oid
|
|
41
|
+
)
|
|
42
|
+
ORDER BY p.proname
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
functions: result.rows,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get all indexes across all tables (excluding system tables)
|
|
52
|
+
*/
|
|
53
|
+
async getIndexes(): Promise<DatabaseIndexesResponse> {
|
|
54
|
+
const pool = this.dbManager.getPool();
|
|
55
|
+
|
|
56
|
+
const result = await pool.query(`
|
|
57
|
+
SELECT
|
|
58
|
+
pi.tablename as "tableName",
|
|
59
|
+
pi.indexname as "indexName",
|
|
60
|
+
pi.indexdef as "indexDef",
|
|
61
|
+
idx.indisunique as "isUnique",
|
|
62
|
+
idx.indisprimary as "isPrimary"
|
|
63
|
+
FROM pg_indexes pi
|
|
64
|
+
JOIN pg_class cls ON cls.relname = pi.indexname
|
|
65
|
+
AND cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = pi.schemaname)
|
|
66
|
+
JOIN pg_index idx ON idx.indexrelid = cls.oid
|
|
67
|
+
WHERE pi.schemaname = 'public'
|
|
68
|
+
AND pi.tablename NOT LIKE '\\_%' ESCAPE '\\'
|
|
69
|
+
ORDER BY pi.tablename, pi.indexname
|
|
70
|
+
`);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
indexes: result.rows,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get all RLS policies across all tables (excluding system tables)
|
|
79
|
+
*/
|
|
80
|
+
async getPolicies(): Promise<DatabasePoliciesResponse> {
|
|
81
|
+
const pool = this.dbManager.getPool();
|
|
82
|
+
|
|
83
|
+
const result = await pool.query(`
|
|
84
|
+
SELECT
|
|
85
|
+
tablename as "tableName",
|
|
86
|
+
policyname as "policyName",
|
|
87
|
+
cmd,
|
|
88
|
+
roles,
|
|
89
|
+
qual,
|
|
90
|
+
with_check as "withCheck"
|
|
91
|
+
FROM pg_policies
|
|
92
|
+
WHERE schemaname = 'public'
|
|
93
|
+
AND tablename NOT LIKE '\\_%' ESCAPE '\\'
|
|
94
|
+
ORDER BY tablename, policyname
|
|
95
|
+
`);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
policies: result.rows,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get all triggers across all tables (excluding system tables)
|
|
104
|
+
*/
|
|
105
|
+
async getTriggers(): Promise<DatabaseTriggersResponse> {
|
|
106
|
+
const pool = this.dbManager.getPool();
|
|
107
|
+
|
|
108
|
+
const result = await pool.query(`
|
|
109
|
+
SELECT
|
|
110
|
+
event_object_table as "tableName",
|
|
111
|
+
trigger_name as "triggerName",
|
|
112
|
+
action_timing as "actionTiming",
|
|
113
|
+
event_manipulation as "eventManipulation",
|
|
114
|
+
action_orientation as "actionOrientation",
|
|
115
|
+
action_condition as "actionCondition",
|
|
116
|
+
action_statement as "actionStatement"
|
|
117
|
+
FROM information_schema.triggers
|
|
118
|
+
WHERE event_object_schema = 'public'
|
|
119
|
+
AND event_object_table NOT LIKE '\\_%' ESCAPE '\\'
|
|
120
|
+
ORDER BY event_object_table, trigger_name
|
|
121
|
+
`);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
triggers: result.rows,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import axios, { AxiosResponse } from 'axios';
|
|
2
|
+
import http from 'http';
|
|
3
|
+
import https from 'https';
|
|
4
|
+
import { TokenManager } from '@/infra/security/token.manager.js';
|
|
5
|
+
import { SecretService } from '@/services/secrets/secret.service.js';
|
|
6
|
+
import logger from '@/utils/logger.js';
|
|
7
|
+
|
|
8
|
+
const postgrestUrl = process.env.POSTGREST_BASE_URL || 'http://localhost:5430';
|
|
9
|
+
|
|
10
|
+
// Connection pooling for PostgREST
|
|
11
|
+
const httpAgent = new http.Agent({
|
|
12
|
+
keepAlive: true,
|
|
13
|
+
keepAliveMsecs: 5000,
|
|
14
|
+
maxSockets: 20,
|
|
15
|
+
maxFreeSockets: 5,
|
|
16
|
+
timeout: 10000,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const httpsAgent = new https.Agent({
|
|
20
|
+
keepAlive: true,
|
|
21
|
+
keepAliveMsecs: 5000,
|
|
22
|
+
maxSockets: 20,
|
|
23
|
+
maxFreeSockets: 5,
|
|
24
|
+
timeout: 10000,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const postgrestAxios = axios.create({
|
|
28
|
+
httpAgent,
|
|
29
|
+
httpsAgent,
|
|
30
|
+
timeout: 10000,
|
|
31
|
+
maxRedirects: 0,
|
|
32
|
+
headers: {
|
|
33
|
+
Connection: 'keep-alive',
|
|
34
|
+
'Keep-Alive': 'timeout=5, max=10',
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export interface ProxyRequest {
|
|
39
|
+
method: string;
|
|
40
|
+
path: string;
|
|
41
|
+
query?: Record<string, unknown>;
|
|
42
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
43
|
+
body?: unknown;
|
|
44
|
+
apiKey?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProxyResponse {
|
|
48
|
+
data: unknown;
|
|
49
|
+
status: number;
|
|
50
|
+
headers: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Headers that should not be forwarded to the client
|
|
55
|
+
*/
|
|
56
|
+
const EXCLUDED_HEADERS = new Set([
|
|
57
|
+
'content-length',
|
|
58
|
+
'transfer-encoding',
|
|
59
|
+
'connection',
|
|
60
|
+
'content-encoding',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
export class PostgrestProxyService {
|
|
64
|
+
private static instance: PostgrestProxyService;
|
|
65
|
+
private tokenManager = TokenManager.getInstance();
|
|
66
|
+
private secretService = SecretService.getInstance();
|
|
67
|
+
private adminToken: string;
|
|
68
|
+
|
|
69
|
+
private constructor() {
|
|
70
|
+
this.adminToken = this.tokenManager.generateAdminToken();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public static getInstance(): PostgrestProxyService {
|
|
74
|
+
if (!PostgrestProxyService.instance) {
|
|
75
|
+
PostgrestProxyService.instance = new PostgrestProxyService();
|
|
76
|
+
}
|
|
77
|
+
return PostgrestProxyService.instance;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Filter headers for forwarding to client (excludes problematic ones)
|
|
82
|
+
*/
|
|
83
|
+
static filterHeaders(headers: Record<string, unknown>): Record<string, string> {
|
|
84
|
+
const filtered: Record<string, string> = {};
|
|
85
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
86
|
+
if (!EXCLUDED_HEADERS.has(key.toLowerCase()) && value !== undefined) {
|
|
87
|
+
filtered[key] = value as string;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return filtered;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Forward request to PostgREST with retry logic
|
|
95
|
+
*/
|
|
96
|
+
async forward(request: ProxyRequest): Promise<ProxyResponse> {
|
|
97
|
+
const targetUrl = `${postgrestUrl}${request.path}`;
|
|
98
|
+
|
|
99
|
+
const axiosConfig: {
|
|
100
|
+
method: string;
|
|
101
|
+
url: string;
|
|
102
|
+
params?: Record<string, unknown>;
|
|
103
|
+
headers: Record<string, string | string[] | undefined>;
|
|
104
|
+
data?: unknown;
|
|
105
|
+
} = {
|
|
106
|
+
method: request.method,
|
|
107
|
+
url: targetUrl,
|
|
108
|
+
params: request.query,
|
|
109
|
+
headers: {
|
|
110
|
+
...request.headers,
|
|
111
|
+
host: undefined,
|
|
112
|
+
'content-length': undefined,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Use admin token if valid API key provided
|
|
117
|
+
if (request.apiKey) {
|
|
118
|
+
const isValid = await this.secretService.verifyApiKey(request.apiKey);
|
|
119
|
+
if (isValid) {
|
|
120
|
+
axiosConfig.headers.authorization = `Bearer ${this.adminToken}`;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (request.body !== undefined) {
|
|
125
|
+
axiosConfig.data = request.body;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Retry logic
|
|
129
|
+
let response: AxiosResponse | undefined;
|
|
130
|
+
let lastError: unknown;
|
|
131
|
+
const maxRetries = 3;
|
|
132
|
+
|
|
133
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
134
|
+
try {
|
|
135
|
+
response = await postgrestAxios(axiosConfig);
|
|
136
|
+
break;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
lastError = error;
|
|
139
|
+
const shouldRetry = axios.isAxiosError(error) && !error.response && attempt < maxRetries;
|
|
140
|
+
|
|
141
|
+
if (shouldRetry) {
|
|
142
|
+
logger.warn(`PostgREST request failed, retrying (attempt ${attempt}/${maxRetries})`, {
|
|
143
|
+
url: targetUrl,
|
|
144
|
+
errorCode: (error as NodeJS.ErrnoException).code,
|
|
145
|
+
message: (error as Error).message,
|
|
146
|
+
});
|
|
147
|
+
const backoffDelay = Math.min(200 * Math.pow(2.5, attempt - 1), 1000);
|
|
148
|
+
await new Promise((resolve) => setTimeout(resolve, backoffDelay));
|
|
149
|
+
} else {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!response) {
|
|
156
|
+
throw lastError || new Error('Failed to get response from PostgREST');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
data: response.data,
|
|
161
|
+
status: response.status,
|
|
162
|
+
headers: response.headers as Record<string, unknown>,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|