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,6 +1,7 @@
|
|
|
1
1
|
import { EmailProvider } from '@/providers/email/base.provider.js';
|
|
2
2
|
import { CloudEmailProvider } from '@/providers/email/cloud.provider.js';
|
|
3
3
|
import { EmailTemplate } from '@/types/email.js';
|
|
4
|
+
import { SendRawEmailRequest } from '@insforge/shared-schemas';
|
|
4
5
|
import logger from '@/utils/logger.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -53,17 +54,14 @@ export class EmailService {
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
/**
|
|
56
|
-
* Send raw email
|
|
57
|
-
* @param
|
|
58
|
-
* @param subject - Email subject
|
|
59
|
-
* @param html - HTML email body
|
|
60
|
-
* @param text - Plain text email body (optional)
|
|
57
|
+
* Send custom/raw email
|
|
58
|
+
* @param options - Email options (to, subject, html, cc, bcc, from, replyTo)
|
|
61
59
|
*/
|
|
62
|
-
public async sendRaw(
|
|
60
|
+
public async sendRaw(options: SendRawEmailRequest): Promise<void> {
|
|
63
61
|
if (!this.provider.sendRaw) {
|
|
64
62
|
throw new Error('Current email provider does not support raw email sending');
|
|
65
63
|
}
|
|
66
|
-
return this.provider.sendRaw(
|
|
64
|
+
return this.provider.sendRaw(options);
|
|
67
65
|
}
|
|
68
66
|
|
|
69
67
|
/**
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
2
2
|
import {
|
|
3
3
|
EdgeFunctionMetadataSchema,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
UploadFunctionRequest,
|
|
5
|
+
UpdateFunctionRequest,
|
|
6
|
+
FunctionSchema,
|
|
7
|
+
ListFunctionsResponse,
|
|
6
8
|
} from '@insforge/shared-schemas';
|
|
7
9
|
import logger from '@/utils/logger.js';
|
|
8
10
|
import { DatabaseError, Pool } from 'pg';
|
|
@@ -10,13 +12,6 @@ import fetch from 'node-fetch';
|
|
|
10
12
|
import { AppError } from '@/api/middlewares/error.js';
|
|
11
13
|
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
12
14
|
|
|
13
|
-
export interface FunctionWithRuntime {
|
|
14
|
-
functions: Record<string, unknown>[];
|
|
15
|
-
runtime: {
|
|
16
|
-
status: 'running' | 'unavailable';
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
|
|
20
15
|
export class FunctionService {
|
|
21
16
|
private static instance: FunctionService;
|
|
22
17
|
private pool: Pool | null = null;
|
|
@@ -41,13 +36,19 @@ export class FunctionService {
|
|
|
41
36
|
/**
|
|
42
37
|
* List all functions with runtime health check
|
|
43
38
|
*/
|
|
44
|
-
async listFunctions(): Promise<
|
|
39
|
+
async listFunctions(): Promise<ListFunctionsResponse> {
|
|
45
40
|
try {
|
|
46
41
|
const result = await this.getPool().query(
|
|
47
42
|
`SELECT
|
|
48
|
-
id,
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
id,
|
|
44
|
+
slug,
|
|
45
|
+
name,
|
|
46
|
+
description,
|
|
47
|
+
status,
|
|
48
|
+
created_at as "createdAt",
|
|
49
|
+
updated_at as "updatedAt",
|
|
50
|
+
deployed_at as "deployedAt"
|
|
51
|
+
FROM functions.definitions
|
|
51
52
|
ORDER BY created_at DESC`
|
|
52
53
|
);
|
|
53
54
|
|
|
@@ -86,13 +87,20 @@ export class FunctionService {
|
|
|
86
87
|
/**
|
|
87
88
|
* Get a specific function by slug
|
|
88
89
|
*/
|
|
89
|
-
async getFunction(slug: string): Promise<
|
|
90
|
+
async getFunction(slug: string): Promise<FunctionSchema | undefined> {
|
|
90
91
|
try {
|
|
91
92
|
const result = await this.getPool().query(
|
|
92
93
|
`SELECT
|
|
93
|
-
id,
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
id,
|
|
95
|
+
slug,
|
|
96
|
+
name,
|
|
97
|
+
description,
|
|
98
|
+
code,
|
|
99
|
+
status,
|
|
100
|
+
created_at as "createdAt",
|
|
101
|
+
updated_at as "updatedAt",
|
|
102
|
+
deployed_at as "deployedAt"
|
|
103
|
+
FROM functions.definitions
|
|
96
104
|
WHERE slug = $1`,
|
|
97
105
|
[slug]
|
|
98
106
|
);
|
|
@@ -111,7 +119,7 @@ export class FunctionService {
|
|
|
111
119
|
/**
|
|
112
120
|
* Create a new function
|
|
113
121
|
*/
|
|
114
|
-
async createFunction(data:
|
|
122
|
+
async createFunction(data: UploadFunctionRequest): Promise<FunctionSchema> {
|
|
115
123
|
const client = await this.getPool().connect();
|
|
116
124
|
try {
|
|
117
125
|
const { name, code, description, status } = data;
|
|
@@ -125,22 +133,23 @@ export class FunctionService {
|
|
|
125
133
|
|
|
126
134
|
// Insert function
|
|
127
135
|
await client.query(
|
|
128
|
-
`INSERT INTO
|
|
136
|
+
`INSERT INTO functions.definitions (id, slug, name, description, code, status)
|
|
129
137
|
VALUES ($1, $2, $3, $4, $5, $6)`,
|
|
130
138
|
[id, slug, name, description || null, code, status]
|
|
131
139
|
);
|
|
132
140
|
|
|
133
141
|
// If status is active, update deployed_at
|
|
134
142
|
if (status === 'active') {
|
|
135
|
-
await client.query(
|
|
136
|
-
id
|
|
137
|
-
|
|
143
|
+
await client.query(
|
|
144
|
+
`UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE id = $1`,
|
|
145
|
+
[id]
|
|
146
|
+
);
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Fetch the created function
|
|
141
150
|
const result = await client.query(
|
|
142
|
-
`SELECT id, slug, name, description, status, created_at
|
|
143
|
-
FROM
|
|
151
|
+
`SELECT id, slug, name, description, status, created_at as "createdAt"
|
|
152
|
+
FROM functions.definitions WHERE id = $1`,
|
|
144
153
|
[id]
|
|
145
154
|
);
|
|
146
155
|
|
|
@@ -176,14 +185,15 @@ export class FunctionService {
|
|
|
176
185
|
*/
|
|
177
186
|
async updateFunction(
|
|
178
187
|
slug: string,
|
|
179
|
-
updates:
|
|
180
|
-
): Promise<
|
|
188
|
+
updates: UpdateFunctionRequest
|
|
189
|
+
): Promise<FunctionSchema | null> {
|
|
181
190
|
const client = await this.getPool().connect();
|
|
182
191
|
try {
|
|
183
192
|
// Check if function exists
|
|
184
|
-
const existingResult = await client.query(
|
|
185
|
-
slug,
|
|
186
|
-
|
|
193
|
+
const existingResult = await client.query(
|
|
194
|
+
'SELECT id FROM functions.definitions WHERE slug = $1',
|
|
195
|
+
[slug]
|
|
196
|
+
);
|
|
187
197
|
if (existingResult.rows.length === 0) {
|
|
188
198
|
return null;
|
|
189
199
|
}
|
|
@@ -195,22 +205,28 @@ export class FunctionService {
|
|
|
195
205
|
|
|
196
206
|
// Update fields
|
|
197
207
|
if (updates.name !== undefined) {
|
|
198
|
-
await client.query('UPDATE
|
|
208
|
+
await client.query('UPDATE functions.definitions SET name = $1 WHERE slug = $2', [
|
|
209
|
+
updates.name,
|
|
210
|
+
slug,
|
|
211
|
+
]);
|
|
199
212
|
}
|
|
200
213
|
|
|
201
214
|
if (updates.description !== undefined) {
|
|
202
|
-
await client.query('UPDATE
|
|
215
|
+
await client.query('UPDATE functions.definitions SET description = $1 WHERE slug = $2', [
|
|
203
216
|
updates.description,
|
|
204
217
|
slug,
|
|
205
218
|
]);
|
|
206
219
|
}
|
|
207
220
|
|
|
208
221
|
if (updates.code !== undefined) {
|
|
209
|
-
await client.query('UPDATE
|
|
222
|
+
await client.query('UPDATE functions.definitions SET code = $1 WHERE slug = $2', [
|
|
223
|
+
updates.code,
|
|
224
|
+
slug,
|
|
225
|
+
]);
|
|
210
226
|
}
|
|
211
227
|
|
|
212
228
|
if (updates.status !== undefined) {
|
|
213
|
-
await client.query('UPDATE
|
|
229
|
+
await client.query('UPDATE functions.definitions SET status = $1 WHERE slug = $2', [
|
|
214
230
|
updates.status,
|
|
215
231
|
slug,
|
|
216
232
|
]);
|
|
@@ -218,21 +234,22 @@ export class FunctionService {
|
|
|
218
234
|
// Update deployed_at if status changes to active
|
|
219
235
|
if (updates.status === 'active') {
|
|
220
236
|
await client.query(
|
|
221
|
-
'UPDATE
|
|
237
|
+
'UPDATE functions.definitions SET deployed_at = CURRENT_TIMESTAMP WHERE slug = $1',
|
|
222
238
|
[slug]
|
|
223
239
|
);
|
|
224
240
|
}
|
|
225
241
|
}
|
|
226
242
|
|
|
227
243
|
// Update updated_at
|
|
228
|
-
await client.query(
|
|
229
|
-
slug,
|
|
230
|
-
|
|
244
|
+
await client.query(
|
|
245
|
+
'UPDATE functions.definitions SET updated_at = CURRENT_TIMESTAMP WHERE slug = $1',
|
|
246
|
+
[slug]
|
|
247
|
+
);
|
|
231
248
|
|
|
232
249
|
// Fetch updated function
|
|
233
250
|
const result = await client.query(
|
|
234
|
-
`SELECT id, slug, name, description, status, updated_at
|
|
235
|
-
FROM
|
|
251
|
+
`SELECT id, slug, name, description, status, updated_at as "updatedAt", deployed_at as "deployedAt"
|
|
252
|
+
FROM functions.definitions WHERE slug = $1`,
|
|
236
253
|
[slug]
|
|
237
254
|
);
|
|
238
255
|
|
|
@@ -254,7 +271,10 @@ export class FunctionService {
|
|
|
254
271
|
*/
|
|
255
272
|
async deleteFunction(slug: string): Promise<boolean> {
|
|
256
273
|
try {
|
|
257
|
-
const result = await this.getPool().query(
|
|
274
|
+
const result = await this.getPool().query(
|
|
275
|
+
'DELETE FROM functions.definitions WHERE slug = $1',
|
|
276
|
+
[slug]
|
|
277
|
+
);
|
|
258
278
|
|
|
259
279
|
if (result.rowCount === 0) {
|
|
260
280
|
return false;
|
|
@@ -278,7 +298,7 @@ export class FunctionService {
|
|
|
278
298
|
try {
|
|
279
299
|
const result = await this.getPool().query(
|
|
280
300
|
`SELECT slug, name, description, status
|
|
281
|
-
FROM
|
|
301
|
+
FROM functions.definitions
|
|
282
302
|
ORDER BY created_at DESC`
|
|
283
303
|
);
|
|
284
304
|
|
|
@@ -35,7 +35,7 @@ export class AuditService {
|
|
|
35
35
|
try {
|
|
36
36
|
const pool = this.getPool();
|
|
37
37
|
const result = await pool.query(
|
|
38
|
-
`INSERT INTO
|
|
38
|
+
`INSERT INTO system.audit_logs (actor, action, module, details, ip_address)
|
|
39
39
|
VALUES ($1, $2, $3, $4, $5)
|
|
40
40
|
RETURNING *`,
|
|
41
41
|
[
|
|
@@ -109,12 +109,12 @@ export class AuditService {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Get total count first
|
|
112
|
-
const countSql = `SELECT COUNT(*) as count FROM
|
|
112
|
+
const countSql = `SELECT COUNT(*) as count FROM system.audit_logs ${whereClause}`;
|
|
113
113
|
const countResult = await pool.query(countSql, params);
|
|
114
114
|
const total = parseInt(countResult.rows[0].count, 10);
|
|
115
115
|
|
|
116
116
|
// Get paginated records
|
|
117
|
-
let dataSql = `SELECT * FROM
|
|
117
|
+
let dataSql = `SELECT * FROM system.audit_logs ${whereClause} ORDER BY created_at DESC`;
|
|
118
118
|
const dataParams = [...params];
|
|
119
119
|
|
|
120
120
|
if (query.limit) {
|
|
@@ -154,7 +154,7 @@ export class AuditService {
|
|
|
154
154
|
async getById(id: string): Promise<AuditLogSchema | null> {
|
|
155
155
|
try {
|
|
156
156
|
const pool = this.getPool();
|
|
157
|
-
const result = await pool.query('SELECT * FROM
|
|
157
|
+
const result = await pool.query('SELECT * FROM system.audit_logs WHERE id = $1', [id]);
|
|
158
158
|
|
|
159
159
|
const row = result.rows[0];
|
|
160
160
|
|
|
@@ -186,30 +186,30 @@ export class AuditService {
|
|
|
186
186
|
startDate.setDate(startDate.getDate() - days);
|
|
187
187
|
|
|
188
188
|
const totalLogsResult = await pool.query(
|
|
189
|
-
'SELECT COUNT(*) as count FROM
|
|
189
|
+
'SELECT COUNT(*) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
190
190
|
[startDate.toISOString()]
|
|
191
191
|
);
|
|
192
192
|
|
|
193
193
|
const uniqueActorsResult = await pool.query(
|
|
194
|
-
'SELECT COUNT(DISTINCT actor) as count FROM
|
|
194
|
+
'SELECT COUNT(DISTINCT actor) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
195
195
|
[startDate.toISOString()]
|
|
196
196
|
);
|
|
197
197
|
|
|
198
198
|
const uniqueModulesResult = await pool.query(
|
|
199
|
-
'SELECT COUNT(DISTINCT module) as count FROM
|
|
199
|
+
'SELECT COUNT(DISTINCT module) as count FROM system.audit_logs WHERE created_at >= $1',
|
|
200
200
|
[startDate.toISOString()]
|
|
201
201
|
);
|
|
202
202
|
|
|
203
203
|
const actionsByModuleResult = await pool.query(
|
|
204
204
|
`SELECT module, COUNT(*) as count
|
|
205
|
-
FROM
|
|
205
|
+
FROM system.audit_logs
|
|
206
206
|
WHERE created_at >= $1
|
|
207
207
|
GROUP BY module`,
|
|
208
208
|
[startDate.toISOString()]
|
|
209
209
|
);
|
|
210
210
|
|
|
211
211
|
const recentActivityResult = await pool.query(
|
|
212
|
-
`SELECT * FROM
|
|
212
|
+
`SELECT * FROM system.audit_logs
|
|
213
213
|
WHERE created_at >= $1
|
|
214
214
|
ORDER BY created_at DESC
|
|
215
215
|
LIMIT 10`,
|
|
@@ -253,7 +253,7 @@ export class AuditService {
|
|
|
253
253
|
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
|
|
254
254
|
|
|
255
255
|
const result = await pool.query(
|
|
256
|
-
'DELETE FROM
|
|
256
|
+
'DELETE FROM system.audit_logs WHERE created_at < $1 RETURNING id',
|
|
257
257
|
[cutoffDate.toISOString()]
|
|
258
258
|
);
|
|
259
259
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { Pool, PoolClient } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
3
|
+
import logger from '@/utils/logger.js';
|
|
4
|
+
import { RoleSchema } from '@insforge/shared-schemas';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles channel authorization by checking RLS policies on the messages table.
|
|
8
|
+
*
|
|
9
|
+
* Permission Model (Supabase pattern):
|
|
10
|
+
* - SELECT on messages = 'join' permission (can subscribe to channel)
|
|
11
|
+
* - INSERT on messages = 'send' permission (can publish to channel)
|
|
12
|
+
*
|
|
13
|
+
* Developers define RLS policies on realtime.messages that check:
|
|
14
|
+
* - current_setting('request.jwt.claim.sub', true) = user ID
|
|
15
|
+
* - current_setting('request.jwt.claim.role', true) = user role
|
|
16
|
+
* - channel_name for channel-specific access
|
|
17
|
+
*/
|
|
18
|
+
export class RealtimeAuthService {
|
|
19
|
+
private static instance: RealtimeAuthService;
|
|
20
|
+
private pool: Pool | null = null;
|
|
21
|
+
|
|
22
|
+
private constructor() {}
|
|
23
|
+
|
|
24
|
+
static getInstance(): RealtimeAuthService {
|
|
25
|
+
if (!RealtimeAuthService.instance) {
|
|
26
|
+
RealtimeAuthService.instance = new RealtimeAuthService();
|
|
27
|
+
}
|
|
28
|
+
return RealtimeAuthService.instance;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private getPool(): Pool {
|
|
32
|
+
if (!this.pool) {
|
|
33
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
34
|
+
}
|
|
35
|
+
return this.pool;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if user has permission to subscribe to a channel.
|
|
40
|
+
* Tests SELECT permission on channels table via RLS.
|
|
41
|
+
*
|
|
42
|
+
* @param channelName - The channel to check access for
|
|
43
|
+
* @param userId - The user ID (undefined for anonymous users)
|
|
44
|
+
* @param role - The database role to use (authenticated or anon)
|
|
45
|
+
* @returns true if user can subscribe, false otherwise
|
|
46
|
+
*/
|
|
47
|
+
async checkSubscribePermission(
|
|
48
|
+
channelName: string,
|
|
49
|
+
userId: string | undefined,
|
|
50
|
+
role: RoleSchema
|
|
51
|
+
): Promise<boolean> {
|
|
52
|
+
const client = await this.getPool().connect();
|
|
53
|
+
try {
|
|
54
|
+
// Begin transaction to ensure settings persist across queries
|
|
55
|
+
await client.query('BEGIN');
|
|
56
|
+
// Switch to specified role to enforce RLS policies
|
|
57
|
+
await client.query(`SET LOCAL ROLE ${role}`);
|
|
58
|
+
await this.setUserContext(client, userId, channelName);
|
|
59
|
+
|
|
60
|
+
// Test SELECT permission via RLS on channels table
|
|
61
|
+
const result = await client.query(
|
|
62
|
+
`SELECT 1 FROM realtime.channels
|
|
63
|
+
WHERE enabled = TRUE
|
|
64
|
+
AND (pattern = $1 OR $1 LIKE pattern)
|
|
65
|
+
LIMIT 1`,
|
|
66
|
+
[channelName]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// Commit transaction
|
|
70
|
+
await client.query('COMMIT');
|
|
71
|
+
|
|
72
|
+
// If query returns a row, user has permission
|
|
73
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
// Rollback transaction on error
|
|
76
|
+
await client.query('ROLLBACK').catch(() => {});
|
|
77
|
+
logger.debug('Subscribe permission denied', { channelName, userId, error });
|
|
78
|
+
return false;
|
|
79
|
+
} finally {
|
|
80
|
+
// Reset role back to default before releasing connection
|
|
81
|
+
await client.query('RESET ROLE');
|
|
82
|
+
client.release();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Set user context variables for RLS policy evaluation.
|
|
88
|
+
* Can be used by other services that need to execute queries with user context.
|
|
89
|
+
*/
|
|
90
|
+
async setUserContext(
|
|
91
|
+
client: PoolClient,
|
|
92
|
+
userId: string | undefined,
|
|
93
|
+
channelName: string
|
|
94
|
+
): Promise<void> {
|
|
95
|
+
if (userId) {
|
|
96
|
+
await client.query("SELECT set_config('request.jwt.claim.sub', $1, true)", [userId]);
|
|
97
|
+
} else {
|
|
98
|
+
await client.query("SELECT set_config('request.jwt.claim.sub', '', true)");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Set the channel being accessed (used by realtime.channel_name())
|
|
102
|
+
await client.query("SELECT set_config('realtime.channel_name', $1, true)", [channelName]);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { Pool } from 'pg';
|
|
2
|
+
import { DatabaseManager } from '@/infra/database/database.manager.js';
|
|
3
|
+
import { AppError } from '@/api/middlewares/error.js';
|
|
4
|
+
import { ERROR_CODES } from '@/types/error-constants.js';
|
|
5
|
+
import logger from '@/utils/logger.js';
|
|
6
|
+
import type {
|
|
7
|
+
RealtimeChannel,
|
|
8
|
+
CreateChannelRequest,
|
|
9
|
+
UpdateChannelRequest,
|
|
10
|
+
RealtimeMetadataSchema,
|
|
11
|
+
RlsPolicy,
|
|
12
|
+
RealtimePermissionsResponse,
|
|
13
|
+
} from '@insforge/shared-schemas';
|
|
14
|
+
|
|
15
|
+
const SYSTEM_POLICIES = ['project_admin_policy'];
|
|
16
|
+
|
|
17
|
+
export class RealtimeChannelService {
|
|
18
|
+
private static instance: RealtimeChannelService;
|
|
19
|
+
private pool: Pool | null = null;
|
|
20
|
+
|
|
21
|
+
private constructor() {}
|
|
22
|
+
|
|
23
|
+
static getInstance(): RealtimeChannelService {
|
|
24
|
+
if (!RealtimeChannelService.instance) {
|
|
25
|
+
RealtimeChannelService.instance = new RealtimeChannelService();
|
|
26
|
+
}
|
|
27
|
+
return RealtimeChannelService.instance;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private getPool(): Pool {
|
|
31
|
+
if (!this.pool) {
|
|
32
|
+
this.pool = DatabaseManager.getInstance().getPool();
|
|
33
|
+
}
|
|
34
|
+
return this.pool;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async list(): Promise<RealtimeChannel[]> {
|
|
38
|
+
const result = await this.getPool().query(`
|
|
39
|
+
SELECT
|
|
40
|
+
id,
|
|
41
|
+
pattern,
|
|
42
|
+
description,
|
|
43
|
+
webhook_urls as "webhookUrls",
|
|
44
|
+
enabled,
|
|
45
|
+
created_at as "createdAt",
|
|
46
|
+
updated_at as "updatedAt"
|
|
47
|
+
FROM realtime.channels
|
|
48
|
+
ORDER BY created_at DESC
|
|
49
|
+
`);
|
|
50
|
+
return result.rows;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getById(id: string): Promise<RealtimeChannel | null> {
|
|
54
|
+
const result = await this.getPool().query(
|
|
55
|
+
`SELECT
|
|
56
|
+
id,
|
|
57
|
+
pattern,
|
|
58
|
+
description,
|
|
59
|
+
webhook_urls as "webhookUrls",
|
|
60
|
+
enabled,
|
|
61
|
+
created_at as "createdAt",
|
|
62
|
+
updated_at as "updatedAt"
|
|
63
|
+
FROM realtime.channels
|
|
64
|
+
WHERE id = $1`,
|
|
65
|
+
[id]
|
|
66
|
+
);
|
|
67
|
+
return result.rows[0] || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find a channel by name (exact match or wildcard pattern match).
|
|
72
|
+
* For wildcard patterns like "order:%", checks if channelName matches the pattern.
|
|
73
|
+
* Returns the matching channel if found and enabled, null otherwise.
|
|
74
|
+
*/
|
|
75
|
+
async getByName(channelName: string): Promise<RealtimeChannel | null> {
|
|
76
|
+
const result = await this.getPool().query(
|
|
77
|
+
`SELECT
|
|
78
|
+
id,
|
|
79
|
+
pattern,
|
|
80
|
+
description,
|
|
81
|
+
webhook_urls as "webhookUrls",
|
|
82
|
+
enabled,
|
|
83
|
+
created_at as "createdAt",
|
|
84
|
+
updated_at as "updatedAt"
|
|
85
|
+
FROM realtime.channels
|
|
86
|
+
WHERE enabled = TRUE
|
|
87
|
+
AND (pattern = $1 OR $1 LIKE pattern)
|
|
88
|
+
ORDER BY pattern = $1 DESC
|
|
89
|
+
LIMIT 1`,
|
|
90
|
+
[channelName]
|
|
91
|
+
);
|
|
92
|
+
return result.rows[0] || null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async create(input: CreateChannelRequest): Promise<RealtimeChannel> {
|
|
96
|
+
this.validateChannelPattern(input.pattern);
|
|
97
|
+
|
|
98
|
+
const result = await this.getPool().query(
|
|
99
|
+
`INSERT INTO realtime.channels (
|
|
100
|
+
pattern, description, webhook_urls, enabled
|
|
101
|
+
) VALUES ($1, $2, $3, $4)
|
|
102
|
+
RETURNING
|
|
103
|
+
id,
|
|
104
|
+
pattern,
|
|
105
|
+
description,
|
|
106
|
+
webhook_urls as "webhookUrls",
|
|
107
|
+
enabled,
|
|
108
|
+
created_at as "createdAt",
|
|
109
|
+
updated_at as "updatedAt"`,
|
|
110
|
+
[input.pattern, input.description || null, input.webhookUrls || null, input.enabled ?? true]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
logger.info('Realtime channel created', { pattern: input.pattern });
|
|
114
|
+
return result.rows[0];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async update(id: string, input: UpdateChannelRequest): Promise<RealtimeChannel> {
|
|
118
|
+
const existing = await this.getById(id);
|
|
119
|
+
if (!existing) {
|
|
120
|
+
throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (input.pattern) {
|
|
124
|
+
this.validateChannelPattern(input.pattern);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const result = await this.getPool().query(
|
|
128
|
+
`UPDATE realtime.channels
|
|
129
|
+
SET
|
|
130
|
+
pattern = COALESCE($2, pattern),
|
|
131
|
+
description = COALESCE($3, description),
|
|
132
|
+
webhook_urls = COALESCE($4, webhook_urls),
|
|
133
|
+
enabled = COALESCE($5, enabled)
|
|
134
|
+
WHERE id = $1
|
|
135
|
+
RETURNING
|
|
136
|
+
id,
|
|
137
|
+
pattern,
|
|
138
|
+
description,
|
|
139
|
+
webhook_urls as "webhookUrls",
|
|
140
|
+
enabled,
|
|
141
|
+
created_at as "createdAt",
|
|
142
|
+
updated_at as "updatedAt"`,
|
|
143
|
+
[id, input.pattern, input.description, input.webhookUrls, input.enabled]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
logger.info('Realtime channel updated', { id });
|
|
147
|
+
return result.rows[0];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async delete(id: string): Promise<void> {
|
|
151
|
+
const existing = await this.getById(id);
|
|
152
|
+
if (!existing) {
|
|
153
|
+
throw new AppError('Channel not found', 404, ERROR_CODES.NOT_FOUND);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
await this.getPool().query('DELETE FROM realtime.channels WHERE id = $1', [id]);
|
|
157
|
+
logger.info('Realtime channel deleted', { id, pattern: existing.pattern });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Get realtime metadata including channels and permissions
|
|
162
|
+
*/
|
|
163
|
+
async getMetadata(): Promise<RealtimeMetadataSchema> {
|
|
164
|
+
const [channels, permissions] = await Promise.all([this.list(), this.getPermissions()]);
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
channels,
|
|
168
|
+
permissions,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ============================================================================
|
|
173
|
+
// Permissions Methods
|
|
174
|
+
// ============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Get RLS policies for a table in the realtime schema, excluding system policies
|
|
178
|
+
*/
|
|
179
|
+
private async getPolicies(tableName: string): Promise<RlsPolicy[]> {
|
|
180
|
+
const result = await this.getPool().query(
|
|
181
|
+
`SELECT
|
|
182
|
+
policyname as "policyName",
|
|
183
|
+
tablename as "tableName",
|
|
184
|
+
cmd as "command",
|
|
185
|
+
roles,
|
|
186
|
+
qual as "using",
|
|
187
|
+
with_check as "withCheck"
|
|
188
|
+
FROM pg_policies
|
|
189
|
+
WHERE schemaname = 'realtime'
|
|
190
|
+
AND tablename = $1
|
|
191
|
+
ORDER BY policyname`,
|
|
192
|
+
[tableName]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Filter out system policies
|
|
196
|
+
return result.rows.filter((policy) => !SYSTEM_POLICIES.includes(policy.policyName));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Get all realtime permissions (RLS policies for channels and messages tables)
|
|
201
|
+
*
|
|
202
|
+
* - Subscribe permission: RLS policies on realtime.channels (SELECT)
|
|
203
|
+
* - Publish permission: RLS policies on realtime.messages (INSERT)
|
|
204
|
+
*/
|
|
205
|
+
async getPermissions(): Promise<RealtimePermissionsResponse> {
|
|
206
|
+
const [channelsPolicies, messagesPolicies] = await Promise.all([
|
|
207
|
+
this.getPolicies('channels'),
|
|
208
|
+
this.getPolicies('messages'),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
subscribe: {
|
|
213
|
+
policies: channelsPolicies,
|
|
214
|
+
},
|
|
215
|
+
publish: {
|
|
216
|
+
policies: messagesPolicies,
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ============================================================================
|
|
222
|
+
// Validation
|
|
223
|
+
// ============================================================================
|
|
224
|
+
|
|
225
|
+
private validateChannelPattern(pattern: string): void {
|
|
226
|
+
// Allow alphanumeric, colons, hyphens, and % for wildcards
|
|
227
|
+
// Note: underscore is not allowed as it's a SQL wildcard character
|
|
228
|
+
const validPattern = /^[a-zA-Z0-9-]+(:[a-zA-Z0-9%:-]+)*$/;
|
|
229
|
+
if (!validPattern.test(pattern)) {
|
|
230
|
+
throw new AppError(
|
|
231
|
+
'Invalid channel pattern. Use alphanumeric characters, colons, hyphens, and % for wildcards.',
|
|
232
|
+
400,
|
|
233
|
+
ERROR_CODES.INVALID_INPUT
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|