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,36 @@
|
|
|
1
|
+
-- Migration: 019 - Create deployments table in system schema
|
|
2
|
+
|
|
3
|
+
-- Create deployments table for tracking deployment requests and their status
|
|
4
|
+
-- Designed to be provider-agnostic (Vercel, Netlify, Cloudflare, etc.)
|
|
5
|
+
--
|
|
6
|
+
-- Status flow:
|
|
7
|
+
-- WAITING -> UPLOADING -> (Vercel statuses: QUEUED/BUILDING/READY/ERROR/CANCELED)
|
|
8
|
+
-- InsForge statuses:
|
|
9
|
+
-- - WAITING: Record created, waiting for client to upload zip to S3
|
|
10
|
+
-- - UPLOADING: Server is downloading from S3 and uploading to Vercel
|
|
11
|
+
-- Vercel statuses (stored directly):
|
|
12
|
+
-- - QUEUED: Deployment queued
|
|
13
|
+
-- - BUILDING: Deployment building
|
|
14
|
+
-- - READY: Deployment ready
|
|
15
|
+
-- - ERROR: Deployment failed
|
|
16
|
+
-- - CANCELED: Deployment canceled
|
|
17
|
+
CREATE TABLE IF NOT EXISTS system.deployments (
|
|
18
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
19
|
+
provider TEXT NOT NULL DEFAULT 'vercel',
|
|
20
|
+
provider_deployment_id TEXT UNIQUE, -- Provider's deployment ID, null until deployment starts
|
|
21
|
+
status TEXT NOT NULL DEFAULT 'WAITING',
|
|
22
|
+
url TEXT,
|
|
23
|
+
metadata JSONB,
|
|
24
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
25
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
-- Create indexes for better query performance
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_status ON system.deployments(status);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_provider ON system.deployments(provider);
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_deployments_created_at ON system.deployments(created_at DESC);
|
|
32
|
+
|
|
33
|
+
-- Add trigger for updated_at
|
|
34
|
+
DROP TRIGGER IF EXISTS update_system_deployments_updated_at ON system.deployments;
|
|
35
|
+
CREATE TRIGGER update_system_deployments_updated_at BEFORE UPDATE ON system.deployments
|
|
36
|
+
FOR EACH ROW EXECUTE FUNCTION system.update_updated_at();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- Migration: 020 - Expand modality constraints to all OpenRouter modalities
|
|
2
|
+
-- Supports: text, image, audio, video, file
|
|
3
|
+
-- Filtering is handled in application layer (shared-schemas + helpers.ts)
|
|
4
|
+
|
|
5
|
+
ALTER TABLE ai.configs DROP CONSTRAINT IF EXISTS check_input_modality_valid;
|
|
6
|
+
ALTER TABLE ai.configs ADD CONSTRAINT check_input_modality_valid
|
|
7
|
+
CHECK (input_modality <@ '{text,image,audio,video,file}'::TEXT[]);
|
|
8
|
+
|
|
9
|
+
ALTER TABLE ai.configs DROP CONSTRAINT IF EXISTS check_output_modality_valid;
|
|
10
|
+
ALTER TABLE ai.configs ADD CONSTRAINT check_output_modality_valid
|
|
11
|
+
CHECK (output_modality <@ '{text,image,audio,video,file}'::TEXT[]);
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootstrap script for migrations table migration
|
|
3
|
+
*
|
|
4
|
+
* This script handles the one-time migration of the node-pg-migrate tracking table
|
|
5
|
+
* from `public._migrations` to `system.migrations`.
|
|
6
|
+
*
|
|
7
|
+
* Why this is needed:
|
|
8
|
+
* - node-pg-migrate checks for the migrations table BEFORE running any migrations
|
|
9
|
+
* - If we try to move the table inside a migration file, node-pg-migrate will have
|
|
10
|
+
* already looked for `system.migrations`, not found it, and created an empty one
|
|
11
|
+
* - This would cause all migrations to appear as "pending" and fail
|
|
12
|
+
*
|
|
13
|
+
* This script runs BEFORE node-pg-migrate and handles the table move gracefully.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import pg from 'pg';
|
|
17
|
+
// Note: This imports a TypeScript file. This works because the script is run with `tsx`
|
|
18
|
+
// (see package.json migrate:bootstrap script), which can handle TypeScript imports.
|
|
19
|
+
// The relative path goes up 4 levels: bootstrap -> migrations -> database -> infra -> src, then into utils.
|
|
20
|
+
import logger from '@/utils/logger.js';
|
|
21
|
+
|
|
22
|
+
const { Pool } = pg;
|
|
23
|
+
|
|
24
|
+
async function bootstrapMigrations() {
|
|
25
|
+
// Use DATABASE_URL from environment (set by dotenv-cli in npm scripts)
|
|
26
|
+
const connectionString = process.env.DATABASE_URL;
|
|
27
|
+
|
|
28
|
+
if (!connectionString) {
|
|
29
|
+
logger.error('DATABASE_URL environment variable is not set');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const pool = new Pool({ connectionString });
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const client = await pool.connect();
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Check if old _migrations table exists in public schema
|
|
40
|
+
const oldTableExists = await client.query(`
|
|
41
|
+
SELECT EXISTS (
|
|
42
|
+
SELECT 1 FROM information_schema.tables
|
|
43
|
+
WHERE table_schema = 'public' AND table_name = '_migrations'
|
|
44
|
+
) as exists
|
|
45
|
+
`);
|
|
46
|
+
|
|
47
|
+
// Check if new system.migrations table already exists
|
|
48
|
+
const newTableExists = await client.query(`
|
|
49
|
+
SELECT EXISTS (
|
|
50
|
+
SELECT 1 FROM information_schema.tables
|
|
51
|
+
WHERE table_schema = 'system' AND table_name = 'migrations'
|
|
52
|
+
) as exists
|
|
53
|
+
`);
|
|
54
|
+
|
|
55
|
+
if (oldTableExists.rows[0].exists && !newTableExists.rows[0].exists) {
|
|
56
|
+
logger.info('Bootstrap: Moving _migrations table to system.migrations...');
|
|
57
|
+
|
|
58
|
+
// Create system schema if it doesn't exist
|
|
59
|
+
await client.query('CREATE SCHEMA IF NOT EXISTS system');
|
|
60
|
+
|
|
61
|
+
// Move the table in a transaction to avoid partial state
|
|
62
|
+
await client.query('BEGIN');
|
|
63
|
+
try {
|
|
64
|
+
await client.query('ALTER TABLE public._migrations SET SCHEMA system');
|
|
65
|
+
await client.query('ALTER TABLE system._migrations RENAME TO migrations');
|
|
66
|
+
await client.query('COMMIT');
|
|
67
|
+
} catch (error) {
|
|
68
|
+
await client.query('ROLLBACK');
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logger.info('Bootstrap: Successfully moved _migrations to system.migrations');
|
|
73
|
+
} else if (newTableExists.rows[0].exists) {
|
|
74
|
+
// Already migrated, nothing to do
|
|
75
|
+
logger.info('Bootstrap: system.migrations already exists, skipping');
|
|
76
|
+
} else if (!oldTableExists.rows[0].exists && !newTableExists.rows[0].exists) {
|
|
77
|
+
// Fresh install - create system schema so node-pg-migrate can create its table there
|
|
78
|
+
logger.info('Bootstrap: No existing migrations table, fresh install');
|
|
79
|
+
await client.query('CREATE SCHEMA IF NOT EXISTS system');
|
|
80
|
+
logger.info('Bootstrap: Created system schema for migrations');
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
client.release();
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
logger.error('Bootstrap migration failed', {
|
|
87
|
+
error: error instanceof Error ? error.message : String(error),
|
|
88
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
89
|
+
});
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
} finally {
|
|
92
|
+
await pool.end();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
bootstrapMigrations().catch((error) => {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
logger.error('Bootstrap migration failed', {
|
|
99
|
+
error: message,
|
|
100
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
101
|
+
});
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
});
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { Client } from 'pg';
|
|
2
|
+
import { SocketManager } from '@/infra/socket/socket.manager.js';
|
|
3
|
+
import { WebhookSender } from './webhook-sender.js';
|
|
4
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
5
|
+
import { RealtimeChannelService } from '@/services/realtime/realtime-channel.service.js';
|
|
6
|
+
import { RealtimeMessageService } from '@/services/realtime/realtime-message.service.js';
|
|
7
|
+
import logger from '@/utils/logger.js';
|
|
8
|
+
import type { RealtimeMessage, RealtimeChannel, WebhookMessage } from '@insforge/shared-schemas';
|
|
9
|
+
import type { DeliveryResult } from '@/types/realtime.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* RealtimeManager - Listens to pg_notify and publishes messages to WebSocket/webhooks
|
|
13
|
+
*
|
|
14
|
+
* This is a singleton that:
|
|
15
|
+
* 1. Maintains a dedicated PostgreSQL connection for LISTEN
|
|
16
|
+
* 2. Receives notifications from realtime.publish() function
|
|
17
|
+
* 3. Publishes messages to WebSocket clients (via Socket.IO rooms)
|
|
18
|
+
* 4. Publishes messages to webhook URLs (via HTTP POST)
|
|
19
|
+
* 5. Updates message records with delivery statistics
|
|
20
|
+
*/
|
|
21
|
+
export class RealtimeManager {
|
|
22
|
+
private static instance: RealtimeManager;
|
|
23
|
+
private listenerClient: Client | null = null;
|
|
24
|
+
private isConnected = false;
|
|
25
|
+
private reconnectTimeout: NodeJS.Timeout | null = null;
|
|
26
|
+
private reconnectAttempts = 0;
|
|
27
|
+
private readonly maxReconnectAttempts = 10;
|
|
28
|
+
private readonly baseReconnectDelay = 5000;
|
|
29
|
+
private webhookSender: WebhookSender;
|
|
30
|
+
|
|
31
|
+
private constructor() {
|
|
32
|
+
this.webhookSender = new WebhookSender();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static getInstance(): RealtimeManager {
|
|
36
|
+
if (!RealtimeManager.instance) {
|
|
37
|
+
RealtimeManager.instance = new RealtimeManager();
|
|
38
|
+
}
|
|
39
|
+
return RealtimeManager.instance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Initialize the realtime manager and start listening for pg_notify
|
|
44
|
+
*/
|
|
45
|
+
async initialize(): Promise<void> {
|
|
46
|
+
if (this.isConnected) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Create a dedicated client for LISTEN (cannot use pooled connections)
|
|
51
|
+
this.listenerClient = DatabaseManager.getInstance().createClient();
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
await this.listenerClient.connect();
|
|
55
|
+
await this.listenerClient.query('LISTEN realtime_message');
|
|
56
|
+
this.isConnected = true;
|
|
57
|
+
this.reconnectAttempts = 0;
|
|
58
|
+
|
|
59
|
+
this.listenerClient.on('notification', (msg) => {
|
|
60
|
+
if (msg.channel === 'realtime_message' && msg.payload) {
|
|
61
|
+
void this.handlePGNotification(msg.payload);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
this.listenerClient.on('error', (error) => {
|
|
66
|
+
logger.error('RealtimeManager connection error', { error: error.message });
|
|
67
|
+
this.handleDisconnect();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
this.listenerClient.on('end', () => {
|
|
71
|
+
logger.warn('RealtimeManager connection ended');
|
|
72
|
+
this.handleDisconnect();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
logger.info('RealtimeManager initialized and listening');
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.error('Failed to initialize RealtimeManager', { error });
|
|
78
|
+
this.handleDisconnect();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle incoming pg_notify notification
|
|
84
|
+
* Payload is just the message_id (UUID string) to bypass 8KB limit
|
|
85
|
+
*/
|
|
86
|
+
private async handlePGNotification(messageId: string): Promise<void> {
|
|
87
|
+
try {
|
|
88
|
+
// 1. Fetch message and channel in parallel
|
|
89
|
+
// channelId is guaranteed non-null for fresh messages (publish/insertMessage validate channel)
|
|
90
|
+
const message = await RealtimeMessageService.getInstance().getById(messageId);
|
|
91
|
+
|
|
92
|
+
if (!message || !message.channelId) {
|
|
93
|
+
logger.warn('Message not found or invalid for realtime notification', { messageId });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. Look up channel configuration (for enabled check and webhook URLs)
|
|
98
|
+
const channel = await RealtimeChannelService.getInstance().getById(message.channelId);
|
|
99
|
+
|
|
100
|
+
if (!channel?.enabled) {
|
|
101
|
+
logger.debug('Channel not found or disabled, skipping', { channelId: message.channelId });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 3. Publish to WebSocket and/or Webhooks
|
|
106
|
+
const result = await this.publishMessage(message, channel);
|
|
107
|
+
|
|
108
|
+
// 4. Update message record with delivery stats
|
|
109
|
+
await RealtimeMessageService.getInstance().updateDeliveryStats(messageId, result);
|
|
110
|
+
|
|
111
|
+
logger.debug('Realtime message published', {
|
|
112
|
+
messageId,
|
|
113
|
+
channelName: message.channelName,
|
|
114
|
+
eventName: message.eventName,
|
|
115
|
+
...result,
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
logger.error('Failed to publish realtime message', { error, messageId });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Publish message to WebSocket clients and webhook URLs
|
|
124
|
+
*/
|
|
125
|
+
private async publishMessage(
|
|
126
|
+
message: RealtimeMessage,
|
|
127
|
+
channel: RealtimeChannel
|
|
128
|
+
): Promise<DeliveryResult> {
|
|
129
|
+
const result: DeliveryResult = {
|
|
130
|
+
wsAudienceCount: 0,
|
|
131
|
+
whAudienceCount: 0,
|
|
132
|
+
whDeliveredCount: 0,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Publish to WebSocket clients
|
|
136
|
+
result.wsAudienceCount = this.publishToWebSocket(message);
|
|
137
|
+
|
|
138
|
+
// Publish to Webhook URLs if configured
|
|
139
|
+
if (channel.webhookUrls && channel.webhookUrls.length > 0) {
|
|
140
|
+
const webhookPayload: WebhookMessage = {
|
|
141
|
+
messageId: message.id,
|
|
142
|
+
channel: message.channelName,
|
|
143
|
+
eventName: message.eventName,
|
|
144
|
+
payload: message.payload,
|
|
145
|
+
};
|
|
146
|
+
const whResult = await this.publishToWebhooks(channel.webhookUrls, webhookPayload);
|
|
147
|
+
result.whAudienceCount = whResult.audienceCount;
|
|
148
|
+
result.whDeliveredCount = whResult.deliveredCount;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Publish message to WebSocket clients subscribed to the channel
|
|
156
|
+
* Returns the number of clients in the room (audience count)
|
|
157
|
+
*/
|
|
158
|
+
private publishToWebSocket(message: RealtimeMessage): number {
|
|
159
|
+
const socketManager = SocketManager.getInstance();
|
|
160
|
+
const roomName = `realtime:${message.channelName}`;
|
|
161
|
+
|
|
162
|
+
const audienceCount = socketManager.getRoomSize(roomName);
|
|
163
|
+
|
|
164
|
+
if (audienceCount > 0) {
|
|
165
|
+
socketManager.broadcastToRoom(
|
|
166
|
+
roomName,
|
|
167
|
+
message.eventName,
|
|
168
|
+
message.payload,
|
|
169
|
+
message.senderType,
|
|
170
|
+
message.senderId ?? undefined,
|
|
171
|
+
message.id
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return audienceCount;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Publish message to all configured webhook URLs
|
|
180
|
+
*/
|
|
181
|
+
private async publishToWebhooks(
|
|
182
|
+
urls: string[],
|
|
183
|
+
message: WebhookMessage
|
|
184
|
+
): Promise<{ audienceCount: number; deliveredCount: number }> {
|
|
185
|
+
const audienceCount = urls.length;
|
|
186
|
+
const results = await this.webhookSender.sendToAll(urls, message);
|
|
187
|
+
const deliveredCount = results.filter((r) => r.success).length;
|
|
188
|
+
|
|
189
|
+
return { audienceCount, deliveredCount };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handle disconnection and attempt reconnection
|
|
194
|
+
*/
|
|
195
|
+
private handleDisconnect(): void {
|
|
196
|
+
this.isConnected = false;
|
|
197
|
+
|
|
198
|
+
if (this.listenerClient) {
|
|
199
|
+
this.listenerClient.removeAllListeners();
|
|
200
|
+
this.listenerClient = null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Reconnect with exponential backoff
|
|
204
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
205
|
+
const delay = this.baseReconnectDelay * Math.pow(2, this.reconnectAttempts);
|
|
206
|
+
this.reconnectAttempts++;
|
|
207
|
+
|
|
208
|
+
if (!this.reconnectTimeout) {
|
|
209
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
210
|
+
this.reconnectTimeout = null;
|
|
211
|
+
logger.info(
|
|
212
|
+
`Attempting to reconnect RealtimeManager (attempt ${this.reconnectAttempts})...`
|
|
213
|
+
);
|
|
214
|
+
void this.initialize();
|
|
215
|
+
}, delay);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
logger.error('RealtimeManager max reconnect attempts reached');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Close the realtime manager connection
|
|
224
|
+
*/
|
|
225
|
+
async close(): Promise<void> {
|
|
226
|
+
if (this.reconnectTimeout) {
|
|
227
|
+
clearTimeout(this.reconnectTimeout);
|
|
228
|
+
this.reconnectTimeout = null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (this.listenerClient) {
|
|
232
|
+
this.listenerClient.removeAllListeners();
|
|
233
|
+
await this.listenerClient.end();
|
|
234
|
+
this.listenerClient = null;
|
|
235
|
+
this.isConnected = false;
|
|
236
|
+
logger.info('RealtimeManager closed');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Check if the manager is connected and healthy
|
|
242
|
+
*/
|
|
243
|
+
isHealthy(): boolean {
|
|
244
|
+
return this.isConnected;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import axios, { AxiosError } from 'axios';
|
|
2
|
+
import logger from '@/utils/logger.js';
|
|
3
|
+
import type { WebhookMessage } from '@insforge/shared-schemas';
|
|
4
|
+
|
|
5
|
+
export interface WebhookResult {
|
|
6
|
+
url: string;
|
|
7
|
+
success: boolean;
|
|
8
|
+
statusCode?: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* WebhookSender - Handles HTTP delivery of realtime messages to webhook endpoints
|
|
14
|
+
*/
|
|
15
|
+
export class WebhookSender {
|
|
16
|
+
private readonly timeout = 10000; // 10 seconds
|
|
17
|
+
private readonly maxRetries = 2;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Send message to all webhook URLs in parallel
|
|
21
|
+
*/
|
|
22
|
+
async sendToAll(urls: string[], message: WebhookMessage): Promise<WebhookResult[]> {
|
|
23
|
+
const promises = urls.map((url) => this.send(url, message));
|
|
24
|
+
return Promise.all(promises);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Send message to a single webhook URL with retry logic
|
|
29
|
+
*/
|
|
30
|
+
private async send(url: string, message: WebhookMessage): Promise<WebhookResult> {
|
|
31
|
+
let lastError: string | undefined;
|
|
32
|
+
|
|
33
|
+
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
34
|
+
try {
|
|
35
|
+
const response = await axios.post(url, message.payload, {
|
|
36
|
+
timeout: this.timeout,
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
'X-InsForge-Event': message.eventName,
|
|
40
|
+
'X-InsForge-Channel': message.channel,
|
|
41
|
+
'X-InsForge-Message-Id': message.messageId,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
url,
|
|
47
|
+
success: response.status >= 200 && response.status < 300,
|
|
48
|
+
statusCode: response.status,
|
|
49
|
+
};
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const axiosError = error as AxiosError;
|
|
52
|
+
lastError = axiosError.message;
|
|
53
|
+
|
|
54
|
+
if (axiosError.response) {
|
|
55
|
+
// Server responded with error status - don't retry
|
|
56
|
+
return {
|
|
57
|
+
url,
|
|
58
|
+
success: false,
|
|
59
|
+
statusCode: axiosError.response.status,
|
|
60
|
+
error: `HTTP ${axiosError.response.status}`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Network error - retry with backoff
|
|
65
|
+
if (attempt < this.maxRetries) {
|
|
66
|
+
await this.delay(1000 * (attempt + 1)); // 1s, 2s
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
logger.warn('Webhook delivery failed after retries', { url, error: lastError });
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
url,
|
|
74
|
+
success: false,
|
|
75
|
+
error: lastError,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private delay(ms: number): Promise<void> {
|
|
80
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
81
|
+
}
|
|
82
|
+
}
|