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
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import jwt from 'jsonwebtoken';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { isCloudEnvironment } from '@/utils/environment.js';
|
|
5
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
6
|
+
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
7
|
+
import { SecretService } from '@/services/secrets/secret.service.js';
|
|
8
|
+
import logger from '@/utils/logger.js';
|
|
9
|
+
|
|
10
|
+
interface CloudCredentialsResponse {
|
|
11
|
+
team_id: string;
|
|
12
|
+
vercel_project_id: string;
|
|
13
|
+
bearer_token: string;
|
|
14
|
+
expires_at: string;
|
|
15
|
+
webhook_secret: string | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface VercelCredentials {
|
|
19
|
+
token: string;
|
|
20
|
+
teamId: string;
|
|
21
|
+
projectId: string;
|
|
22
|
+
expiresAt: Date | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface VercelDeploymentResult {
|
|
26
|
+
id: string;
|
|
27
|
+
url: string | null;
|
|
28
|
+
state: string;
|
|
29
|
+
readyState: string;
|
|
30
|
+
name: string;
|
|
31
|
+
createdAt: Date;
|
|
32
|
+
error?: {
|
|
33
|
+
code: string;
|
|
34
|
+
message: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface CreateDeploymentOptions {
|
|
39
|
+
name?: string;
|
|
40
|
+
files?: Array<{
|
|
41
|
+
file: string;
|
|
42
|
+
sha: string;
|
|
43
|
+
size: number;
|
|
44
|
+
}>;
|
|
45
|
+
projectSettings?: {
|
|
46
|
+
buildCommand?: string | null;
|
|
47
|
+
outputDirectory?: string | null;
|
|
48
|
+
installCommand?: string | null;
|
|
49
|
+
devCommand?: string | null;
|
|
50
|
+
rootDirectory?: string | null;
|
|
51
|
+
};
|
|
52
|
+
meta?: Record<string, string>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface DeploymentFile {
|
|
56
|
+
path: string;
|
|
57
|
+
content: Buffer;
|
|
58
|
+
sha: string;
|
|
59
|
+
size: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export class VercelProvider {
|
|
63
|
+
private static instance: VercelProvider;
|
|
64
|
+
private cloudCredentials: VercelCredentials | undefined;
|
|
65
|
+
private fetchPromise: Promise<VercelCredentials> | null = null;
|
|
66
|
+
private secretService: SecretService;
|
|
67
|
+
|
|
68
|
+
private constructor() {
|
|
69
|
+
this.secretService = SecretService.getInstance();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getInstance(): VercelProvider {
|
|
73
|
+
if (!VercelProvider.instance) {
|
|
74
|
+
VercelProvider.instance = new VercelProvider();
|
|
75
|
+
}
|
|
76
|
+
return VercelProvider.instance;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get Vercel credentials based on environment
|
|
81
|
+
*/
|
|
82
|
+
async getCredentials(): Promise<VercelCredentials> {
|
|
83
|
+
if (isCloudEnvironment()) {
|
|
84
|
+
if (
|
|
85
|
+
this.cloudCredentials &&
|
|
86
|
+
(!this.cloudCredentials.expiresAt || new Date() < this.cloudCredentials.expiresAt)
|
|
87
|
+
) {
|
|
88
|
+
return this.cloudCredentials;
|
|
89
|
+
}
|
|
90
|
+
return await this.fetchCloudCredentials();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const token = process.env.VERCEL_TOKEN;
|
|
94
|
+
const teamId = process.env.VERCEL_TEAM_ID;
|
|
95
|
+
const projectId = process.env.VERCEL_PROJECT_ID;
|
|
96
|
+
|
|
97
|
+
if (!token) {
|
|
98
|
+
throw new AppError(
|
|
99
|
+
'VERCEL_TOKEN not found in environment variables',
|
|
100
|
+
500,
|
|
101
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (!teamId) {
|
|
105
|
+
throw new AppError(
|
|
106
|
+
'VERCEL_TEAM_ID not found in environment variables',
|
|
107
|
+
500,
|
|
108
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!projectId) {
|
|
112
|
+
throw new AppError(
|
|
113
|
+
'VERCEL_PROJECT_ID not found in environment variables',
|
|
114
|
+
500,
|
|
115
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { token, teamId, projectId, expiresAt: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if Vercel is properly configured
|
|
124
|
+
*/
|
|
125
|
+
isConfigured(): boolean {
|
|
126
|
+
if (isCloudEnvironment()) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
return !!(
|
|
130
|
+
process.env.VERCEL_TOKEN &&
|
|
131
|
+
process.env.VERCEL_TEAM_ID &&
|
|
132
|
+
process.env.VERCEL_PROJECT_ID
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Fetch credentials from cloud service
|
|
138
|
+
*/
|
|
139
|
+
private async fetchCloudCredentials(): Promise<VercelCredentials> {
|
|
140
|
+
if (this.fetchPromise) {
|
|
141
|
+
logger.info('Vercel credentials fetch already in progress, waiting for completion...');
|
|
142
|
+
return this.fetchPromise;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.fetchPromise = (async () => {
|
|
146
|
+
try {
|
|
147
|
+
const projectId = process.env.PROJECT_ID;
|
|
148
|
+
if (!projectId) {
|
|
149
|
+
throw new Error('PROJECT_ID not found in environment variables');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
153
|
+
if (!jwtSecret) {
|
|
154
|
+
throw new Error('JWT_SECRET not found in environment variables');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const signature = jwt.sign({ projectId }, jwtSecret, { expiresIn: '1h' });
|
|
158
|
+
|
|
159
|
+
const response = await fetch(
|
|
160
|
+
`${process.env.CLOUD_API_HOST || 'https://api.insforge.dev'}/sites/v1/credentials/${projectId}?sign=${signature}`
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(`Failed to fetch Vercel credentials: ${response.statusText}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const data = (await response.json()) as CloudCredentialsResponse;
|
|
168
|
+
|
|
169
|
+
if (!data.bearer_token || !data.vercel_project_id) {
|
|
170
|
+
throw new Error('Invalid response: missing Vercel credentials');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (data.webhook_secret) {
|
|
174
|
+
await this.storeWebhookSecret(data.webhook_secret);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.cloudCredentials = {
|
|
178
|
+
token: data.bearer_token,
|
|
179
|
+
teamId: data.team_id,
|
|
180
|
+
projectId: data.vercel_project_id,
|
|
181
|
+
expiresAt: new Date(data.expires_at),
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
logger.info('Successfully fetched Vercel credentials from cloud', {
|
|
185
|
+
expiresAt: this.cloudCredentials.expiresAt?.toISOString(),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return this.cloudCredentials;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logger.error('Failed to fetch Vercel credentials', {
|
|
191
|
+
error: error instanceof Error ? error.message : String(error),
|
|
192
|
+
});
|
|
193
|
+
throw error;
|
|
194
|
+
} finally {
|
|
195
|
+
this.fetchPromise = null;
|
|
196
|
+
}
|
|
197
|
+
})();
|
|
198
|
+
|
|
199
|
+
return this.fetchPromise;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Store webhook secret in secrets service
|
|
204
|
+
*/
|
|
205
|
+
private async storeWebhookSecret(webhookSecret: string): Promise<void> {
|
|
206
|
+
const secretKey = 'VERCEL_WEBHOOK_SECRET';
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const existingSecret = await this.secretService.getSecretByKey(secretKey);
|
|
210
|
+
|
|
211
|
+
if (existingSecret === webhookSecret) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (existingSecret !== null) {
|
|
216
|
+
await this.secretService.updateSecretByKey(secretKey, { value: webhookSecret });
|
|
217
|
+
logger.info('Vercel webhook secret updated');
|
|
218
|
+
} else {
|
|
219
|
+
await this.secretService.createSecret({
|
|
220
|
+
key: secretKey,
|
|
221
|
+
value: webhookSecret,
|
|
222
|
+
isReserved: true,
|
|
223
|
+
});
|
|
224
|
+
logger.info('Vercel webhook secret created');
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
logger.warn('Failed to store Vercel webhook secret', {
|
|
228
|
+
error: error instanceof Error ? error.message : String(error),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Create a new deployment on Vercel
|
|
235
|
+
* POST /v13/deployments
|
|
236
|
+
*/
|
|
237
|
+
async createDeployment(options: CreateDeploymentOptions = {}): Promise<VercelDeploymentResult> {
|
|
238
|
+
const credentials = await this.getCredentials();
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
const response = await axios.post(
|
|
242
|
+
`https://api.vercel.com/v13/deployments?teamId=${credentials.teamId}&skipAutoDetectionConfirmation=1`,
|
|
243
|
+
{
|
|
244
|
+
name: options.name || 'deployment',
|
|
245
|
+
target: 'production',
|
|
246
|
+
project: credentials.projectId,
|
|
247
|
+
files: options.files,
|
|
248
|
+
projectSettings: options.projectSettings,
|
|
249
|
+
meta: options.meta,
|
|
250
|
+
},
|
|
251
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const deployment = response.data;
|
|
255
|
+
|
|
256
|
+
logger.info('Vercel deployment created', {
|
|
257
|
+
id: deployment.id,
|
|
258
|
+
url: deployment.url,
|
|
259
|
+
readyState: deployment.readyState,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
id: deployment.id,
|
|
264
|
+
url: deployment.url ? `https://${deployment.url}` : null,
|
|
265
|
+
state: deployment.readyState,
|
|
266
|
+
readyState: deployment.readyState,
|
|
267
|
+
name: deployment.name,
|
|
268
|
+
createdAt: new Date(deployment.createdAt),
|
|
269
|
+
};
|
|
270
|
+
} catch (error) {
|
|
271
|
+
logger.error('Failed to create Vercel deployment', {
|
|
272
|
+
error: error instanceof Error ? error.message : String(error),
|
|
273
|
+
});
|
|
274
|
+
throw new AppError('Failed to create Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get deployment status by deployment ID
|
|
280
|
+
* GET /v13/deployments/:id
|
|
281
|
+
*/
|
|
282
|
+
async getDeployment(deploymentId: string): Promise<VercelDeploymentResult> {
|
|
283
|
+
const credentials = await this.getCredentials();
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const response = await axios.get(
|
|
287
|
+
`https://api.vercel.com/v13/deployments/${deploymentId}?teamId=${credentials.teamId}`,
|
|
288
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
289
|
+
);
|
|
290
|
+
const deployment = response.data;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
id: deployment.id,
|
|
294
|
+
url: deployment.url ? `https://${deployment.url}` : null,
|
|
295
|
+
state: deployment.readyState,
|
|
296
|
+
readyState: deployment.readyState,
|
|
297
|
+
name: deployment.name,
|
|
298
|
+
createdAt: new Date(deployment.createdAt),
|
|
299
|
+
error: deployment.errorCode
|
|
300
|
+
? {
|
|
301
|
+
code: deployment.errorCode,
|
|
302
|
+
message: deployment.errorMessage || 'Unknown error',
|
|
303
|
+
}
|
|
304
|
+
: undefined,
|
|
305
|
+
};
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
|
308
|
+
throw new AppError(`Deployment not found: ${deploymentId}`, 404, ERROR_CODES.NOT_FOUND);
|
|
309
|
+
}
|
|
310
|
+
logger.error('Failed to get Vercel deployment', {
|
|
311
|
+
error: error instanceof Error ? error.message : String(error),
|
|
312
|
+
deploymentId,
|
|
313
|
+
});
|
|
314
|
+
throw new AppError('Failed to get Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Cancel a deployment
|
|
320
|
+
* PATCH /v12/deployments/:id/cancel
|
|
321
|
+
*/
|
|
322
|
+
async cancelDeployment(deploymentId: string): Promise<void> {
|
|
323
|
+
const credentials = await this.getCredentials();
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
await axios.patch(
|
|
327
|
+
`https://api.vercel.com/v12/deployments/${deploymentId}/cancel?teamId=${credentials.teamId}`,
|
|
328
|
+
{},
|
|
329
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
330
|
+
);
|
|
331
|
+
logger.info('Vercel deployment cancelled', { deploymentId });
|
|
332
|
+
} catch (error) {
|
|
333
|
+
logger.error('Failed to cancel Vercel deployment', {
|
|
334
|
+
error: error instanceof Error ? error.message : String(error),
|
|
335
|
+
deploymentId,
|
|
336
|
+
});
|
|
337
|
+
throw new AppError('Failed to cancel Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Upsert environment variables for the project
|
|
343
|
+
* POST /v10/projects/:id/env
|
|
344
|
+
*/
|
|
345
|
+
async upsertEnvironmentVariables(envVars: Array<{ key: string; value: string }>): Promise<void> {
|
|
346
|
+
const credentials = await this.getCredentials();
|
|
347
|
+
|
|
348
|
+
try {
|
|
349
|
+
const payload = envVars.map((env) => ({
|
|
350
|
+
key: env.key,
|
|
351
|
+
value: env.value,
|
|
352
|
+
type: 'encrypted',
|
|
353
|
+
target: ['production', 'preview', 'development'],
|
|
354
|
+
}));
|
|
355
|
+
|
|
356
|
+
await axios.post(
|
|
357
|
+
`https://api.vercel.com/v10/projects/${credentials.projectId}/env?teamId=${credentials.teamId}&upsert=true`,
|
|
358
|
+
payload,
|
|
359
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
logger.info('Environment variables upserted', {
|
|
363
|
+
count: envVars.length,
|
|
364
|
+
keys: envVars.map((e) => e.key),
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
logger.error('Failed to upsert environment variables', {
|
|
368
|
+
error: error instanceof Error ? error.message : String(error),
|
|
369
|
+
});
|
|
370
|
+
throw new AppError('Failed to upsert environment variables', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get all environment variable keys for the project
|
|
376
|
+
* GET /v9/projects/:id/env
|
|
377
|
+
*/
|
|
378
|
+
async getEnvironmentVariableKeys(): Promise<string[]> {
|
|
379
|
+
const credentials = await this.getCredentials();
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
const response = await axios.get(
|
|
383
|
+
`https://api.vercel.com/v9/projects/${credentials.projectId}/env?teamId=${credentials.teamId}`,
|
|
384
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
const data = response.data as { envs?: Array<{ key: string }> };
|
|
388
|
+
return (data.envs || []).map((env) => env.key);
|
|
389
|
+
} catch (error) {
|
|
390
|
+
logger.warn('Failed to get environment variable keys', {
|
|
391
|
+
error: error instanceof Error ? error.message : String(error),
|
|
392
|
+
});
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Clear cached credentials
|
|
399
|
+
*/
|
|
400
|
+
clearCredentials(): void {
|
|
401
|
+
this.cloudCredentials = undefined;
|
|
402
|
+
this.fetchPromise = null;
|
|
403
|
+
logger.info('Vercel credentials cache cleared');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Upload a single file to Vercel
|
|
408
|
+
* POST /v2/files
|
|
409
|
+
*/
|
|
410
|
+
async uploadFile(fileContent: Buffer): Promise<string> {
|
|
411
|
+
const credentials = await this.getCredentials();
|
|
412
|
+
const sha = this.computeSha(fileContent);
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
await axios.post(
|
|
416
|
+
`https://api.vercel.com/v2/files?teamId=${credentials.teamId}`,
|
|
417
|
+
fileContent,
|
|
418
|
+
{
|
|
419
|
+
headers: {
|
|
420
|
+
Authorization: `Bearer ${credentials.token}`,
|
|
421
|
+
'Content-Type': 'application/octet-stream',
|
|
422
|
+
'Content-Length': fileContent.length.toString(),
|
|
423
|
+
'x-vercel-digest': sha,
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
logger.info('File uploaded to Vercel', { sha, size: fileContent.length });
|
|
429
|
+
return sha;
|
|
430
|
+
} catch (error) {
|
|
431
|
+
// 409 Conflict means file already exists (same SHA), which is fine
|
|
432
|
+
if (axios.isAxiosError(error) && error.response?.status === 409) {
|
|
433
|
+
logger.info('File already exists on Vercel', { sha });
|
|
434
|
+
return sha;
|
|
435
|
+
}
|
|
436
|
+
logger.error('Failed to upload file to Vercel', {
|
|
437
|
+
error: error instanceof Error ? error.message : String(error),
|
|
438
|
+
});
|
|
439
|
+
throw new AppError('Failed to upload file to Vercel', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Upload multiple files to Vercel in parallel
|
|
445
|
+
*/
|
|
446
|
+
async uploadFiles(
|
|
447
|
+
files: Array<{ path: string; content: Buffer }>
|
|
448
|
+
): Promise<Array<{ file: string; sha: string; size: number }>> {
|
|
449
|
+
const uploadPromises = files.map(async ({ path, content }) => {
|
|
450
|
+
const sha = await this.uploadFile(content);
|
|
451
|
+
return {
|
|
452
|
+
file: path,
|
|
453
|
+
sha,
|
|
454
|
+
size: content.length,
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
return Promise.all(uploadPromises);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Compute SHA-1 hash of file content
|
|
463
|
+
*/
|
|
464
|
+
private computeSha(content: Buffer): string {
|
|
465
|
+
return crypto.createHash('sha1').update(content).digest('hex');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Create deployment using file SHAs (files must be pre-uploaded)
|
|
470
|
+
*/
|
|
471
|
+
async createDeploymentWithFiles(
|
|
472
|
+
files: Array<{ file: string; sha: string; size: number }>,
|
|
473
|
+
options: Omit<CreateDeploymentOptions, 'files'> = {}
|
|
474
|
+
): Promise<VercelDeploymentResult> {
|
|
475
|
+
const credentials = await this.getCredentials();
|
|
476
|
+
|
|
477
|
+
try {
|
|
478
|
+
const response = await axios.post(
|
|
479
|
+
`https://api.vercel.com/v13/deployments?teamId=${credentials.teamId}&skipAutoDetectionConfirmation=1`,
|
|
480
|
+
{
|
|
481
|
+
name: options.name || 'deployment',
|
|
482
|
+
target: 'production',
|
|
483
|
+
project: credentials.projectId,
|
|
484
|
+
files: files,
|
|
485
|
+
projectSettings: options.projectSettings,
|
|
486
|
+
meta: options.meta,
|
|
487
|
+
},
|
|
488
|
+
{ headers: { Authorization: `Bearer ${credentials.token}` } }
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
const deployment = response.data;
|
|
492
|
+
|
|
493
|
+
logger.info('Vercel deployment created with file SHAs', {
|
|
494
|
+
id: deployment.id,
|
|
495
|
+
url: deployment.url,
|
|
496
|
+
readyState: deployment.readyState,
|
|
497
|
+
fileCount: files.length,
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
id: deployment.id,
|
|
502
|
+
url: deployment.url ? `https://${deployment.url}` : null,
|
|
503
|
+
state: deployment.readyState,
|
|
504
|
+
readyState: deployment.readyState,
|
|
505
|
+
name: deployment.name,
|
|
506
|
+
createdAt: new Date(deployment.createdAt),
|
|
507
|
+
};
|
|
508
|
+
} catch (error) {
|
|
509
|
+
logger.error('Failed to create Vercel deployment with files', {
|
|
510
|
+
error: error instanceof Error ? error.message : String(error),
|
|
511
|
+
fileCount: files.length,
|
|
512
|
+
});
|
|
513
|
+
throw new AppError('Failed to create Vercel deployment', 500, ERROR_CODES.INTERNAL_ERROR);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EmailTemplate } from '@/types/email.js';
|
|
2
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Email provider interface
|
|
@@ -25,14 +26,10 @@ export interface EmailProvider {
|
|
|
25
26
|
): Promise<void>;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
|
-
* Send raw email
|
|
29
|
-
*
|
|
30
|
-
* @param to - Recipient email address
|
|
31
|
-
* @param subject - Email subject
|
|
32
|
-
* @param html - HTML email body
|
|
33
|
-
* @param text - Plain text email body (optional)
|
|
29
|
+
* Send custom/raw email (optional - not all providers may support this)
|
|
30
|
+
* @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
|
|
34
31
|
*/
|
|
35
|
-
sendRaw?(
|
|
32
|
+
sendRaw?(options: SendRawEmailRequest): Promise<void>;
|
|
36
33
|
|
|
37
34
|
/**
|
|
38
35
|
* Check if provider supports template-based emails
|
|
@@ -5,6 +5,7 @@ import logger from '@/utils/logger.js';
|
|
|
5
5
|
import { AppError } from '@/api/middlewares/error.js';
|
|
6
6
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
7
7
|
import { EmailTemplate } from '@/types/email.js';
|
|
8
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
8
9
|
import { EmailProvider } from './base.provider.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -184,4 +185,87 @@ export class CloudEmailProvider implements EmailProvider {
|
|
|
184
185
|
);
|
|
185
186
|
}
|
|
186
187
|
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Send custom/raw email via cloud backend
|
|
191
|
+
*/
|
|
192
|
+
async sendRaw(options: SendRawEmailRequest): Promise<void> {
|
|
193
|
+
try {
|
|
194
|
+
const projectId = config.cloud.projectId;
|
|
195
|
+
const apiHost = config.cloud.apiHost;
|
|
196
|
+
const signToken = this.generateSignToken();
|
|
197
|
+
|
|
198
|
+
const url = `${apiHost}/email/v1/${projectId}/send-on-demand`;
|
|
199
|
+
const response = await axios.post(url, options, {
|
|
200
|
+
headers: {
|
|
201
|
+
'Content-Type': 'application/json',
|
|
202
|
+
sign: signToken,
|
|
203
|
+
},
|
|
204
|
+
timeout: 10000,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (response.data?.success) {
|
|
208
|
+
logger.info('Raw email sent successfully', { projectId });
|
|
209
|
+
} else {
|
|
210
|
+
throw new AppError(
|
|
211
|
+
'Email service returned unsuccessful response',
|
|
212
|
+
500,
|
|
213
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
if (axios.isAxiosError(error)) {
|
|
218
|
+
const status = error.response?.status;
|
|
219
|
+
const message = error.response?.data?.message || error.message;
|
|
220
|
+
|
|
221
|
+
logger.error('Failed to send raw email via cloud backend', {
|
|
222
|
+
projectId: config.cloud.projectId,
|
|
223
|
+
status,
|
|
224
|
+
message,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (status === 401) {
|
|
228
|
+
throw new AppError(
|
|
229
|
+
'Authentication failed with cloud email service.',
|
|
230
|
+
status,
|
|
231
|
+
ERROR_CODES.AUTH_UNAUTHORIZED
|
|
232
|
+
);
|
|
233
|
+
} else if (status === 403) {
|
|
234
|
+
throw new AppError(
|
|
235
|
+
'Custom email service is not available for free plan. Please upgrade to use this feature.',
|
|
236
|
+
status,
|
|
237
|
+
ERROR_CODES.FORBIDDEN
|
|
238
|
+
);
|
|
239
|
+
} else if (status === 429) {
|
|
240
|
+
throw new AppError(
|
|
241
|
+
'Email rate limit exceeded. Starter plan is limited 10 emails per hour, and Pro plan is limited 50 emails per hour',
|
|
242
|
+
status,
|
|
243
|
+
ERROR_CODES.RATE_LIMITED
|
|
244
|
+
);
|
|
245
|
+
} else if (status === 400) {
|
|
246
|
+
throw new AppError(
|
|
247
|
+
`Invalid email request: ${message}`,
|
|
248
|
+
status,
|
|
249
|
+
ERROR_CODES.INVALID_INPUT
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
throw new AppError(
|
|
253
|
+
`Failed to send email: ${message}`,
|
|
254
|
+
status || 500,
|
|
255
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (error instanceof AppError) {
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
throw new AppError(
|
|
265
|
+
`Failed to send email: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
266
|
+
500,
|
|
267
|
+
ERROR_CODES.INTERNAL_ERROR
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
187
271
|
}
|