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
|
@@ -1,125 +1,216 @@
|
|
|
1
|
-
import jwt from 'jsonwebtoken';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (error instanceof AppError) {
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
1
|
+
import jwt from 'jsonwebtoken';
|
|
2
|
+
import crypto from 'crypto';
|
|
3
|
+
import { createRemoteJWKSet, JWTPayload, jwtVerify } from 'jose';
|
|
4
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
5
|
+
import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
|
|
6
|
+
import type { TokenPayloadSchema } from '@insforge/shared-schemas';
|
|
7
|
+
|
|
8
|
+
const JWT_SECRET = process.env.JWT_SECRET ?? '';
|
|
9
|
+
// TODO: Change access token expiration time to 15 min
|
|
10
|
+
const JWT_EXPIRES_IN = '7d';
|
|
11
|
+
const REFRESH_TOKEN_EXPIRES_IN = '7d';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Refresh token payload interface
|
|
15
|
+
*/
|
|
16
|
+
export interface RefreshTokenPayload {
|
|
17
|
+
sub: string;
|
|
18
|
+
type: 'refresh';
|
|
19
|
+
iss: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create JWKS instance with caching and timeout configuration
|
|
24
|
+
* The instance will automatically cache keys and handle refetching
|
|
25
|
+
*/
|
|
26
|
+
const cloudApiHost = process.env.CLOUD_API_HOST || 'https://api.insforge.dev';
|
|
27
|
+
const JWKS = createRemoteJWKSet(new URL(`${cloudApiHost}/.well-known/jwks.json`), {
|
|
28
|
+
timeoutDuration: 10000, // 10 second timeout for HTTP requests
|
|
29
|
+
cooldownDuration: 30000, // 30 seconds cooldown after successful fetch
|
|
30
|
+
cacheMaxAge: 600000, // Maximum 10 minutes between refetches
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* TokenManager - Handles JWT token operations
|
|
35
|
+
* Infrastructure layer for token generation and verification
|
|
36
|
+
*/
|
|
37
|
+
export class TokenManager {
|
|
38
|
+
private static instance: TokenManager;
|
|
39
|
+
|
|
40
|
+
private constructor() {
|
|
41
|
+
if (!process.env.JWT_SECRET) {
|
|
42
|
+
throw new Error('JWT_SECRET environment variable is required');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public static getInstance(): TokenManager {
|
|
47
|
+
if (!TokenManager.instance) {
|
|
48
|
+
TokenManager.instance = new TokenManager();
|
|
49
|
+
}
|
|
50
|
+
return TokenManager.instance;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generate JWT access token for users and admins
|
|
55
|
+
*/
|
|
56
|
+
generateToken(payload: TokenPayloadSchema): string {
|
|
57
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
58
|
+
algorithm: 'HS256',
|
|
59
|
+
expiresIn: JWT_EXPIRES_IN,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Generate admin JWT token (never expires)
|
|
65
|
+
* Used for internal API key authenticated requests to PostgREST
|
|
66
|
+
*/
|
|
67
|
+
generateAdminToken(): string {
|
|
68
|
+
const payload = {
|
|
69
|
+
sub: 'project-admin-with-api-key',
|
|
70
|
+
email: 'project-admin@email.com',
|
|
71
|
+
role: 'project_admin',
|
|
72
|
+
};
|
|
73
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
74
|
+
algorithm: 'HS256',
|
|
75
|
+
// No expiresIn means token never expires
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Generate refresh token for secure session management
|
|
81
|
+
*/
|
|
82
|
+
generateRefreshToken(userId: string): string {
|
|
83
|
+
const refreshPayload: RefreshTokenPayload = {
|
|
84
|
+
sub: userId,
|
|
85
|
+
type: 'refresh',
|
|
86
|
+
iss: 'insforge',
|
|
87
|
+
};
|
|
88
|
+
return jwt.sign(refreshPayload, JWT_SECRET, {
|
|
89
|
+
algorithm: 'HS256',
|
|
90
|
+
expiresIn: REFRESH_TOKEN_EXPIRES_IN,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Verify refresh token and return payload
|
|
96
|
+
* Ensures the token is a valid refresh token (not an access token)
|
|
97
|
+
*/
|
|
98
|
+
verifyRefreshToken(token: string): RefreshTokenPayload {
|
|
99
|
+
try {
|
|
100
|
+
const decoded = jwt.verify(token, JWT_SECRET, {
|
|
101
|
+
algorithms: ['HS256'],
|
|
102
|
+
issuer: 'insforge',
|
|
103
|
+
}) as RefreshTokenPayload;
|
|
104
|
+
|
|
105
|
+
// Ensure this is a refresh token, not an access token
|
|
106
|
+
if (decoded.type !== 'refresh' || !decoded.sub) {
|
|
107
|
+
throw new AppError('Invalid refresh token type', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return decoded;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (error instanceof AppError) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
throw new AppError('Invalid or expired refresh token', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Generate anonymous JWT token (never expires)
|
|
121
|
+
*/
|
|
122
|
+
generateAnonToken(): string {
|
|
123
|
+
const payload = {
|
|
124
|
+
sub: '12345678-1234-5678-90ab-cdef12345678',
|
|
125
|
+
email: 'anon@insforge.com',
|
|
126
|
+
role: 'anon',
|
|
127
|
+
};
|
|
128
|
+
return jwt.sign(payload, JWT_SECRET, {
|
|
129
|
+
algorithm: 'HS256',
|
|
130
|
+
// No expiresIn means token never expires
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Verify JWT token
|
|
136
|
+
*/
|
|
137
|
+
verifyToken(token: string): TokenPayloadSchema {
|
|
138
|
+
try {
|
|
139
|
+
const decoded = jwt.verify(token, JWT_SECRET) as TokenPayloadSchema;
|
|
140
|
+
return {
|
|
141
|
+
sub: decoded.sub,
|
|
142
|
+
email: decoded.email,
|
|
143
|
+
role: decoded.role || 'authenticated',
|
|
144
|
+
};
|
|
145
|
+
} catch {
|
|
146
|
+
throw new AppError('Invalid token', 401, ERROR_CODES.AUTH_UNAUTHORIZED);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Verify cloud backend JWT token
|
|
152
|
+
* Validates JWT tokens from api.insforge.dev using JWKS
|
|
153
|
+
*/
|
|
154
|
+
async verifyCloudToken(token: string): Promise<{ projectId: string; payload: JWTPayload }> {
|
|
155
|
+
try {
|
|
156
|
+
// JWKS handles caching internally, no need to manage it manually
|
|
157
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
158
|
+
algorithms: ['RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512'],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Verify project_id matches if configured
|
|
162
|
+
const tokenProjectId = payload['projectId'] as string;
|
|
163
|
+
const expectedProjectId = process.env.PROJECT_ID;
|
|
164
|
+
|
|
165
|
+
if (expectedProjectId && tokenProjectId !== expectedProjectId) {
|
|
166
|
+
throw new AppError(
|
|
167
|
+
'Project ID mismatch',
|
|
168
|
+
403,
|
|
169
|
+
ERROR_CODES.AUTH_UNAUTHORIZED,
|
|
170
|
+
NEXT_ACTION.CHECK_TOKEN
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
projectId: tokenProjectId || expectedProjectId || 'local',
|
|
176
|
+
payload,
|
|
177
|
+
};
|
|
178
|
+
} catch (error) {
|
|
179
|
+
// Re-throw AppError as-is
|
|
180
|
+
if (error instanceof AppError) {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Wrap other JWT errors
|
|
185
|
+
throw new AppError(
|
|
186
|
+
`Invalid cloud authorization code: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
187
|
+
401,
|
|
188
|
+
ERROR_CODES.AUTH_INVALID_CREDENTIALS,
|
|
189
|
+
NEXT_ACTION.CHECK_TOKEN
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Generate CSRF token derived from refresh token using HMAC
|
|
196
|
+
*/
|
|
197
|
+
generateCsrfToken(refreshToken: string): string {
|
|
198
|
+
return crypto.createHmac('sha256', JWT_SECRET).update(refreshToken).digest('hex');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Verify CSRF token by re-computing from refresh token
|
|
203
|
+
* Uses timing-safe comparison to prevent timing attacks
|
|
204
|
+
*/
|
|
205
|
+
verifyCsrfToken(csrfHeader: string | undefined, refreshToken: string): boolean {
|
|
206
|
+
if (!csrfHeader || !refreshToken) {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const expectedCsrf = this.generateCsrfToken(refreshToken);
|
|
210
|
+
try {
|
|
211
|
+
return crypto.timingSafeEqual(Buffer.from(csrfHeader), Buffer.from(expectedCsrf));
|
|
212
|
+
} catch {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -2,17 +2,19 @@ import { Server as HttpServer } from 'http';
|
|
|
2
2
|
import { Server as SocketIOServer, Socket } from 'socket.io';
|
|
3
3
|
import logger from '@/utils/logger.js';
|
|
4
4
|
import { TokenManager } from '@/infra/security/token.manager.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { ServerEvents, ClientEvents, SocketMetadata, NotificationPayload } from '@/types/socket.js';
|
|
6
|
+
import type {
|
|
7
|
+
SubscribeChannelPayload,
|
|
8
|
+
PublishEventPayload,
|
|
8
9
|
SocketMessage,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from '@/types/socket.js';
|
|
10
|
+
SocketMessageMeta,
|
|
11
|
+
SubscribeResponse,
|
|
12
|
+
UnsubscribeChannelPayload,
|
|
13
|
+
} from '@insforge/shared-schemas';
|
|
14
14
|
import { AppError } from '@/api/middlewares/error.js';
|
|
15
15
|
import { ERROR_CODES, NEXT_ACTION } from '@/types/error-constants.js';
|
|
16
|
+
import { RealtimeAuthService } from '@/services/realtime/realtime-auth.service.js';
|
|
17
|
+
import { RealtimeMessageService } from '@/services/realtime/realtime-message.service.js';
|
|
16
18
|
|
|
17
19
|
const tokenManager = TokenManager.getInstance();
|
|
18
20
|
|
|
@@ -188,14 +190,22 @@ export class SocketManager {
|
|
|
188
190
|
* Setup handlers for client events
|
|
189
191
|
*/
|
|
190
192
|
private setupClientEventHandlers(socket: Socket): void {
|
|
191
|
-
// Handle
|
|
192
|
-
socket.on(
|
|
193
|
-
|
|
193
|
+
// Handle realtime channel subscribe with ack callback
|
|
194
|
+
socket.on(
|
|
195
|
+
ClientEvents.REALTIME_SUBSCRIBE,
|
|
196
|
+
(payload: SubscribeChannelPayload, ack: (response: SubscribeResponse) => void) => {
|
|
197
|
+
void this.handleRealtimeSubscribe(socket, payload, ack);
|
|
198
|
+
}
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Handle realtime channel unsubscribe (fire-and-forget, no ack needed)
|
|
202
|
+
socket.on(ClientEvents.REALTIME_UNSUBSCRIBE, (payload: UnsubscribeChannelPayload) => {
|
|
203
|
+
this.handleRealtimeUnsubscribe(socket, payload);
|
|
194
204
|
});
|
|
195
205
|
|
|
196
|
-
// Handle
|
|
197
|
-
socket.on(ClientEvents.
|
|
198
|
-
this.
|
|
206
|
+
// Handle realtime publish (client-initiated messages)
|
|
207
|
+
socket.on(ClientEvents.REALTIME_PUBLISH, (payload: PublishEventPayload) => {
|
|
208
|
+
void this.handleRealtimePublish(socket, payload);
|
|
199
209
|
});
|
|
200
210
|
|
|
201
211
|
// Update last activity on any event
|
|
@@ -208,70 +218,181 @@ export class SocketManager {
|
|
|
208
218
|
}
|
|
209
219
|
|
|
210
220
|
/**
|
|
211
|
-
* Handle channel
|
|
221
|
+
* Handle realtime channel subscribe request
|
|
212
222
|
*/
|
|
213
|
-
private
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
223
|
+
private async handleRealtimeSubscribe(
|
|
224
|
+
socket: Socket,
|
|
225
|
+
payload: SubscribeChannelPayload,
|
|
226
|
+
ack?: (response: SubscribeResponse) => void
|
|
227
|
+
): Promise<void> {
|
|
228
|
+
const authService = RealtimeAuthService.getInstance();
|
|
229
|
+
const { channel } = payload;
|
|
230
|
+
const userId = socket.data.user?.id;
|
|
231
|
+
const userRole = socket.data.user?.role;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
// Check subscribe permission via RLS SELECT policy
|
|
235
|
+
const canSubscribe = await authService.checkSubscribePermission(channel, userId, userRole);
|
|
236
|
+
|
|
237
|
+
if (!canSubscribe) {
|
|
238
|
+
ack?.({
|
|
239
|
+
ok: false,
|
|
240
|
+
channel,
|
|
241
|
+
error: { code: 'UNAUTHORIZED', message: 'Not authorized to subscribe to this channel' },
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const roomName = `realtime:${channel}`;
|
|
247
|
+
await socket.join(roomName);
|
|
248
|
+
|
|
249
|
+
const metadata = this.socketMetadata.get(socket.id);
|
|
250
|
+
if (metadata) {
|
|
251
|
+
metadata.subscriptions.add(roomName);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ack?.({ ok: true, channel });
|
|
255
|
+
|
|
256
|
+
logger.debug('Socket subscribed to realtime channel', {
|
|
257
|
+
socketId: socket.id,
|
|
258
|
+
channel,
|
|
259
|
+
});
|
|
260
|
+
} catch (error) {
|
|
261
|
+
logger.error('Error handling realtime subscribe', { error, channel });
|
|
262
|
+
ack?.({
|
|
263
|
+
ok: false,
|
|
264
|
+
channel,
|
|
265
|
+
error: { code: 'INTERNAL_ERROR', message: 'Failed to subscribe to channel' },
|
|
266
|
+
});
|
|
217
267
|
}
|
|
268
|
+
}
|
|
218
269
|
|
|
219
|
-
|
|
220
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Handle realtime channel unsubscribe request (fire-and-forget)
|
|
272
|
+
*/
|
|
273
|
+
private handleRealtimeUnsubscribe(socket: Socket, payload: UnsubscribeChannelPayload): void {
|
|
274
|
+
const { channel } = payload;
|
|
275
|
+
const roomName = `realtime:${channel}`;
|
|
221
276
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
277
|
+
void socket.leave(roomName);
|
|
278
|
+
|
|
279
|
+
const metadata = this.socketMetadata.get(socket.id);
|
|
280
|
+
if (metadata) {
|
|
281
|
+
metadata.subscriptions.delete(roomName);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
logger.debug('Socket unsubscribed from realtime channel', { socketId: socket.id, channel });
|
|
226
285
|
}
|
|
227
286
|
|
|
228
287
|
/**
|
|
229
|
-
* Handle
|
|
288
|
+
* Handle realtime publish request (client-initiated message)
|
|
289
|
+
* Inserts message to DB - trigger handles pg_notify, broadcast, and stats update.
|
|
230
290
|
*/
|
|
231
|
-
private
|
|
291
|
+
private async handleRealtimePublish(socket: Socket, payload: PublishEventPayload): Promise<void> {
|
|
292
|
+
const { channel, event, payload: eventPayload } = payload;
|
|
293
|
+
const userId = socket.data.user?.id;
|
|
294
|
+
const userRole = socket.data.user?.role;
|
|
295
|
+
|
|
296
|
+
// Check if client has subscribed to this channel
|
|
297
|
+
const roomName = `realtime:${channel}`;
|
|
232
298
|
const metadata = this.socketMetadata.get(socket.id);
|
|
233
|
-
if (!metadata) {
|
|
299
|
+
if (!metadata?.subscriptions.has(roomName)) {
|
|
300
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
301
|
+
channel,
|
|
302
|
+
code: 'NOT_SUBSCRIBED',
|
|
303
|
+
message: 'Must subscribe to channel before publishing messages',
|
|
304
|
+
});
|
|
234
305
|
return;
|
|
235
306
|
}
|
|
236
307
|
|
|
237
|
-
|
|
238
|
-
|
|
308
|
+
try {
|
|
309
|
+
// Insert message directly - trigger will handle pg_notify and broadcasting
|
|
310
|
+
const messageService = RealtimeMessageService.getInstance();
|
|
311
|
+
const result = await messageService.insertMessage(
|
|
312
|
+
channel,
|
|
313
|
+
event,
|
|
314
|
+
eventPayload,
|
|
315
|
+
userId,
|
|
316
|
+
userRole
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
if (!result) {
|
|
320
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
321
|
+
channel,
|
|
322
|
+
code: 'UNAUTHORIZED',
|
|
323
|
+
message: 'Not authorized to publish to this channel',
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
239
327
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
328
|
+
logger.debug('Client message inserted', {
|
|
329
|
+
socketId: socket.id,
|
|
330
|
+
channel,
|
|
331
|
+
event,
|
|
332
|
+
});
|
|
333
|
+
} catch (error) {
|
|
334
|
+
logger.error('Error handling realtime publish', { error, channel });
|
|
335
|
+
socket.emit(ServerEvents.REALTIME_ERROR, {
|
|
336
|
+
channel,
|
|
337
|
+
code: 'INTERNAL_ERROR',
|
|
338
|
+
message: 'Failed to publish message',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
244
341
|
}
|
|
245
342
|
|
|
246
343
|
/**
|
|
247
|
-
*
|
|
344
|
+
* Build a SocketMessage with meta and payload
|
|
248
345
|
*/
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
346
|
+
private buildSocketMessage<T extends object>(
|
|
347
|
+
payload: T,
|
|
348
|
+
meta: Omit<SocketMessageMeta, 'messageId' | 'timestamp'> & { messageId?: string }
|
|
349
|
+
): SocketMessage & T {
|
|
350
|
+
return {
|
|
351
|
+
...payload,
|
|
352
|
+
meta: {
|
|
353
|
+
...meta,
|
|
354
|
+
messageId: meta.messageId || this.generateMessageId(),
|
|
355
|
+
timestamp: new Date().toISOString(),
|
|
356
|
+
},
|
|
357
|
+
} as SocketMessage & T;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Emit message to specific socket
|
|
362
|
+
*/
|
|
363
|
+
emitToSocket<T extends object>(
|
|
364
|
+
socket: Socket,
|
|
365
|
+
event: string,
|
|
366
|
+
payload: T,
|
|
367
|
+
senderType: 'system' | 'user' = 'system',
|
|
368
|
+
senderId?: string,
|
|
369
|
+
messageId?: string
|
|
370
|
+
): void {
|
|
371
|
+
const message = this.buildSocketMessage(payload, {
|
|
372
|
+
channel: socket.id,
|
|
373
|
+
senderType,
|
|
374
|
+
senderId,
|
|
375
|
+
messageId,
|
|
376
|
+
});
|
|
256
377
|
socket.emit(event, message);
|
|
257
378
|
}
|
|
258
379
|
|
|
259
380
|
/**
|
|
260
381
|
* Broadcast to all connected clients
|
|
261
382
|
*/
|
|
262
|
-
broadcastToAll<T>(
|
|
383
|
+
broadcastToAll<T extends object>(
|
|
384
|
+
event: string,
|
|
385
|
+
payload: T,
|
|
386
|
+
senderType: 'system' | 'user' = 'system',
|
|
387
|
+
senderId?: string,
|
|
388
|
+
messageId?: string
|
|
389
|
+
): void {
|
|
263
390
|
if (!this.io) {
|
|
264
391
|
logger.warn('Socket.IO server not initialized');
|
|
265
392
|
return;
|
|
266
393
|
}
|
|
267
394
|
|
|
268
|
-
const message
|
|
269
|
-
type: event,
|
|
270
|
-
payload,
|
|
271
|
-
timestamp: Date.now(),
|
|
272
|
-
id: this.generateMessageId(),
|
|
273
|
-
};
|
|
274
|
-
|
|
395
|
+
const message = this.buildSocketMessage(payload, { senderType, senderId, messageId });
|
|
275
396
|
this.io.emit(event, message);
|
|
276
397
|
|
|
277
398
|
logger.info('Broadcasted message to all clients', {
|
|
@@ -283,25 +404,38 @@ export class SocketManager {
|
|
|
283
404
|
/**
|
|
284
405
|
* Broadcast to specific room
|
|
285
406
|
*/
|
|
286
|
-
broadcastToRoom<T>(
|
|
407
|
+
broadcastToRoom<T extends object>(
|
|
408
|
+
room: string,
|
|
409
|
+
event: string,
|
|
410
|
+
payload: T,
|
|
411
|
+
senderType: 'system' | 'user',
|
|
412
|
+
senderId?: string,
|
|
413
|
+
messageId?: string
|
|
414
|
+
): void {
|
|
287
415
|
if (!this.io) {
|
|
288
416
|
logger.warn('Socket.IO server not initialized');
|
|
289
417
|
return;
|
|
290
418
|
}
|
|
291
419
|
|
|
292
|
-
const message
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
|
|
420
|
+
const message = this.buildSocketMessage(payload, {
|
|
421
|
+
channel: room,
|
|
422
|
+
senderType,
|
|
423
|
+
senderId,
|
|
424
|
+
messageId,
|
|
425
|
+
});
|
|
299
426
|
this.io.to(room).emit(event, message);
|
|
300
427
|
|
|
301
|
-
logger.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
428
|
+
logger.debug('Broadcasted message to room', { event, room });
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Get the number of sockets in a room
|
|
433
|
+
*/
|
|
434
|
+
getRoomSize(room: string): number {
|
|
435
|
+
if (!this.io) {
|
|
436
|
+
return 0;
|
|
437
|
+
}
|
|
438
|
+
return this.io.sockets.adapter.rooms.get(room)?.size || 0;
|
|
305
439
|
}
|
|
306
440
|
|
|
307
441
|
/**
|
|
@@ -368,11 +502,11 @@ export class SocketManager {
|
|
|
368
502
|
close(): void {
|
|
369
503
|
if (this.io) {
|
|
370
504
|
// Notify all clients about server shutdown
|
|
371
|
-
this.broadcastToAll
|
|
505
|
+
this.broadcastToAll(ServerEvents.NOTIFICATION, {
|
|
372
506
|
level: 'warning',
|
|
373
507
|
title: 'Server Shutdown',
|
|
374
508
|
message: 'Server is shutting down',
|
|
375
|
-
});
|
|
509
|
+
} as NotificationPayload);
|
|
376
510
|
|
|
377
511
|
// Close all connections
|
|
378
512
|
void this.io.close();
|